From b30bdbbb9271c71ec8d6b0d56148578f7bad5a33 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Tue, 15 Oct 2024 23:35:06 -0400 Subject: [PATCH 001/372] fix(profiling): stack v2 properly tracks asyncio tasks (#11024) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> --- .../profiling/stack_v2/include/sampler.hpp | 15 ++- .../profiling/stack_v2/src/sampler.cpp | 31 +++++ .../profiling/stack_v2/src/stack_v2.cpp | 52 ++++++++ ddtrace/profiling/_asyncio.py | 33 ++++- ...tack-v2-asyncio-task-4f9ead040829f2f5.yaml | 5 + .../collector/test_stack_asyncio.py | 114 ++++++++++++++++ .../profiling_v2/collector/test_threading.py | 124 +++++++++++------- 7 files changed, 323 insertions(+), 51 deletions(-) create mode 100644 releasenotes/notes/profiling-stack-v2-asyncio-task-4f9ead040829f2f5.yaml create mode 100644 tests/profiling_v2/collector/test_stack_asyncio.py diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/sampler.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/sampler.hpp index aab60fa59e7..7050b6fcaa4 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/sampler.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/sampler.hpp @@ -41,11 +41,16 @@ class Sampler void stop(); void register_thread(uint64_t id, uint64_t native_id, const char* name); void unregister_thread(uint64_t id); - - // The Python side dynamically adjusts the sampling rate based on overhead, so we need to be able to update our own - // intervals accordingly. Rather than a preemptive measure, we assume the rate is ~fairly stable and just update - // the next rate with the latest interval. This is not perfect because the adjustment is based on self-time, and - // we're not currently accounting for the echion self-time. + void track_asyncio_loop(uintptr_t thread_id, PyObject* loop); + void init_asyncio(PyObject* _asyncio_current_tasks, + PyObject* _asyncio_scheduled_tasks, + PyObject* _asyncio_eager_tasks); + void link_tasks(PyObject* parent, PyObject* child); + + // The Python side dynamically adjusts the sampling rate based on overhead, so we need to be able to update our + // own intervals accordingly. Rather than a preemptive measure, we assume the rate is ~fairly stable and just + // update the next rate with the latest interval. This is not perfect because the adjustment is based on + // self-time, and we're not currently accounting for the echion self-time. void set_interval(double new_interval); }; diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp index 3f02e20cd43..c05ae45477e 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp @@ -149,3 +149,34 @@ Sampler::stop() // a sampling loop. Currently there is no mechanism to force stuck threads, should they get locked. ++thread_seq_num; } + +void +Sampler::track_asyncio_loop(uintptr_t thread_id, PyObject* loop) +{ + // Holds echion's global lock + std::lock_guard guard(thread_info_map_lock); + if (thread_info_map.find(thread_id) != thread_info_map.end()) { + thread_info_map.find(thread_id)->second->asyncio_loop = + (loop != Py_None) ? reinterpret_cast(loop) : 0; + } +} + +void +Sampler::init_asyncio(PyObject* _asyncio_current_tasks, + PyObject* _asyncio_scheduled_tasks, + PyObject* _asyncio_eager_tasks) +{ + asyncio_current_tasks = _asyncio_current_tasks; + asyncio_scheduled_tasks = _asyncio_scheduled_tasks; + asyncio_eager_tasks = _asyncio_eager_tasks; + if (asyncio_eager_tasks == Py_None) { + asyncio_eager_tasks = NULL; + } +} + +void +Sampler::link_tasks(PyObject* parent, PyObject* child) +{ + std::lock_guard guard(task_link_map_lock); + task_link_map[child] = parent; +} diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp index 62fa4b38b77..ee7338f0cbd 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp @@ -115,6 +115,54 @@ _stack_v2_link_span(PyObject* self, PyObject* args, PyObject* kwargs) PyCFunction stack_v2_link_span = cast_to_pycfunction(_stack_v2_link_span); +static PyObject* +stack_v2_track_asyncio_loop(PyObject* self, PyObject* args) +{ + (void)self; + uintptr_t thread_id; // map key + PyObject* loop; + + if (!PyArg_ParseTuple(args, "lO", &thread_id, &loop)) { + return NULL; + } + + Sampler::get().track_asyncio_loop(thread_id, loop); + + Py_RETURN_NONE; +} + +static PyObject* +stack_v2_init_asyncio(PyObject* self, PyObject* args) +{ + (void)self; + PyObject* asyncio_current_tasks; + PyObject* asyncio_scheduled_tasks; + PyObject* asyncio_eager_tasks; + + if (!PyArg_ParseTuple(args, "OOO", &asyncio_current_tasks, &asyncio_scheduled_tasks, &asyncio_eager_tasks)) { + return NULL; + } + + Sampler::get().init_asyncio(asyncio_current_tasks, asyncio_scheduled_tasks, asyncio_eager_tasks); + + Py_RETURN_NONE; +} + +static PyObject* +stack_v2_link_tasks(PyObject* self, PyObject* args) +{ + (void)self; + PyObject *parent, *child; + + if (!PyArg_ParseTuple(args, "OO", &parent, &child)) { + return NULL; + } + + Sampler::get().link_tasks(parent, child); + + Py_RETURN_NONE; +} + static PyMethodDef _stack_v2_methods[] = { { "start", reinterpret_cast(stack_v2_start), METH_VARARGS | METH_KEYWORDS, "Start the sampler" }, { "stop", stack_v2_stop, METH_VARARGS, "Stop the sampler" }, @@ -125,6 +173,10 @@ static PyMethodDef _stack_v2_methods[] = { reinterpret_cast(stack_v2_link_span), METH_VARARGS | METH_KEYWORDS, "Link a span to a thread" }, + // asyncio task support + { "track_asyncio_loop", stack_v2_track_asyncio_loop, METH_VARARGS, "Map the name of a task with its identifier" }, + { "init_asyncio", stack_v2_init_asyncio, METH_VARARGS, "Initialise asyncio tracking" }, + { "link_tasks", stack_v2_link_tasks, METH_VARARGS, "Link two tasks" }, { NULL, NULL, 0, NULL } }; diff --git a/ddtrace/profiling/_asyncio.py b/ddtrace/profiling/_asyncio.py index eebcf2c30b1..3d6d18c11bb 100644 --- a/ddtrace/profiling/_asyncio.py +++ b/ddtrace/profiling/_asyncio.py @@ -4,9 +4,12 @@ from types import ModuleType # noqa:F401 import typing # noqa:F401 +from ddtrace.internal._unpatched import _threading as ddtrace_threading +from ddtrace.internal.datadog.profiling import stack_v2 from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.utils import get_argument_value from ddtrace.internal.wrapping import wrap +from ddtrace.settings.profiling import config from . import _threading @@ -48,16 +51,44 @@ def _(asyncio): if THREAD_LINK is None: THREAD_LINK = _threading._ThreadLink() + init_stack_v2 = config.stack.v2_enabled and stack_v2.is_available + @partial(wrap, sys.modules["asyncio.events"].BaseDefaultEventLoopPolicy.set_event_loop) def _(f, args, kwargs): + loop = get_argument_value(args, kwargs, 1, "loop") try: + if init_stack_v2: + stack_v2.track_asyncio_loop(typing.cast(int, ddtrace_threading.current_thread().ident), loop) return f(*args, **kwargs) finally: THREAD_LINK.clear_threads(set(sys._current_frames().keys())) - loop = get_argument_value(args, kwargs, 1, "loop") if loop is not None: THREAD_LINK.link_object(loop) + if init_stack_v2: + + @partial(wrap, sys.modules["asyncio"].tasks._GatheringFuture.__init__) + def _(f, args, kwargs): + try: + return f(*args, **kwargs) + finally: + children = get_argument_value(args, kwargs, 1, "children") + # Pass an invalid positional index for 'loop' + loop = get_argument_value(args, kwargs, -1, "loop") + # Link the parent gathering task to the gathered children + parent = globals()["current_task"](loop) + for child in children: + stack_v2.link_tasks(parent, child) + + if sys.hexversion >= 0x030C0000: + scheduled_tasks = asyncio.tasks._scheduled_tasks.data + eager_tasks = asyncio.tasks._eager_tasks + else: + scheduled_tasks = asyncio.tasks._all_tasks.data + eager_tasks = None + + stack_v2.init_asyncio(asyncio.tasks._current_tasks, scheduled_tasks, eager_tasks) # type: ignore[attr-defined] + def get_event_loop_for_thread(thread_id): global THREAD_LINK diff --git a/releasenotes/notes/profiling-stack-v2-asyncio-task-4f9ead040829f2f5.yaml b/releasenotes/notes/profiling-stack-v2-asyncio-task-4f9ead040829f2f5.yaml new file mode 100644 index 00000000000..61778b13b88 --- /dev/null +++ b/releasenotes/notes/profiling-stack-v2-asyncio-task-4f9ead040829f2f5.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: this resolves an issue where asyncio task names are not captured + by stack v2, when ``DD_PROFILING_STACK_V2_ENABLED`` is set. diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling_v2/collector/test_stack_asyncio.py new file mode 100644 index 00000000000..1af4f227238 --- /dev/null +++ b/tests/profiling_v2/collector/test_stack_asyncio.py @@ -0,0 +1,114 @@ +import asyncio +import glob +import os +import sys +import time + +import pytest + +from ddtrace.internal.datadog.profiling import stack_v2 +from ddtrace.profiling import _asyncio +from ddtrace.profiling import profiler +from ddtrace.settings.profiling import config +from tests.profiling.collector import _asyncio_compat +from tests.profiling.collector import pprof_utils + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason="stack v2 is available only on 3.8+ as echion does") +def test_asyncio(monkeypatch): + pprof_output_prefix = "/tmp/test_asyncio" + monkeypatch.setattr(config.stack, "v2_enabled", True) + monkeypatch.setattr(config, "output_pprof", pprof_output_prefix) + + assert stack_v2.is_available, stack_v2.failure_msg + + sleep_time = 0.2 + loop_run_time = 3 + + async def stuff() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def hello(): + t1 = _asyncio_compat.create_task(stuff(), name="sleep 1") + t2 = _asyncio_compat.create_task(stuff(), name="sleep 2") + await stuff() + return (t1, t2) + + p = profiler.Profiler() + p.start() + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + if _asyncio_compat.PY38_AND_LATER: + maintask = loop.create_task(hello(), name="main") + else: + maintask = loop.create_task(hello()) + + t1, t2 = loop.run_until_complete(maintask) + p.stop() + + t1_name = _asyncio._task_get_name(t1) + t2_name = _asyncio._task_get_name(t2) + + assert t1_name == "sleep 1" + assert t2_name == "sleep 2" + + output_filename = pprof_output_prefix + "." + str(os.getpid()) + + profile = pprof_utils.parse_profile(output_filename) + + # get samples with task_name + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + # The next fails if stack_v2 is not properly configured with asyncio task + # tracking via ddtrace.profiling._asyncio + assert len(samples) > 0 + + # We'd like to check whether there exist samples with + # 1. task name label "main" + # - function name label "hello" + # - and line number is between + # 2. task name label t1_name or t2_name + # - function name label "stuff" + # And they all have thread name "MainThread" + + checked_main = False + checked_t1 = False + checked_t2 = False + + for sample in samples: + task_name_label = pprof_utils.get_label_with_key(profile.string_table, sample, "task name") + task_name = profile.string_table[task_name_label.str] + + thread_name_label = pprof_utils.get_label_with_key(profile.string_table, sample, "thread name") + thread_name = profile.string_table[thread_name_label.str] + + location_id = sample.location_id[0] + location = pprof_utils.get_location_with_id(profile, location_id) + line = location.line[0] + function = pprof_utils.get_function_with_id(profile, line.function_id) + function_name = profile.string_table[function.name] + + if task_name == "main": + assert thread_name == "MainThread" + assert function_name == "hello" + checked_main = True + elif task_name == t1_name or task_name == t2_name: + assert thread_name == "MainThread" + assert function_name == "stuff" + if task_name == t1_name: + checked_t1 = True + if task_name == t2_name: + checked_t2 = True + + assert checked_main + assert checked_t1 + assert checked_t2 + + # cleanup output file + for f in glob.glob(pprof_output_prefix + ".*"): + try: + os.remove(f) + except Exception as e: + print("Error removing file: {}".format(e)) + pass diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 1af87cc9eb9..abed7d0cfda 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -190,6 +190,85 @@ def test_wrapt_disable_extensions(): ) +# This test has to be run in a subprocess because it calls gevent.monkey.patch_all() +# which affects the whole process. +@pytest.mark.skipif(not TESTING_GEVENT, reason="gevent is not available") +@pytest.mark.subprocess( + env=dict(DD_PROFILING_FILE_PATH=__file__), +) +def test_lock_gevent_tasks(): + from gevent import monkey + + monkey.patch_all() + + import glob + import os + import threading + + from ddtrace.internal.datadog.profiling import ddup + from ddtrace.profiling.collector import threading as collector_threading + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.lock_utils import get_lock_linenos + from tests.profiling.collector.lock_utils import init_linenos + + assert ddup.is_available, "ddup is not available" + + # Set up the ddup exporter + test_name = "test_lock_gevent_tasks" + pprof_prefix = "/tmp" + os.sep + test_name + output_filename = pprof_prefix + "." + str(os.getpid()) + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + init_linenos(os.environ["DD_PROFILING_FILE_PATH"]) + + def play_with_lock(): + lock = threading.Lock() # !CREATE! test_lock_gevent_tasks + lock.acquire() # !ACQUIRE! test_lock_gevent_tasks + lock.release() # !RELEASE! test_lock_gevent_tasks + + with collector_threading.ThreadingLockCollector(None, capture_pct=100, export_libdd_enabled=True): + t = threading.Thread(name="foobar", target=play_with_lock) + t.start() + t.join() + + ddup.upload() + + expected_filename = "test_threading.py" + linenos = get_lock_linenos(test_name) + + profile = pprof_utils.parse_profile(output_filename) + pprof_utils.assert_lock_events( + profile, + expected_acquire_events=[ + pprof_utils.LockAcquireEvent( + caller_name="play_with_lock", + filename=expected_filename, + linenos=linenos, + lock_name="lock", + task_id=t.ident, + task_name="foobar", + ), + ], + expected_release_events=[ + pprof_utils.LockReleaseEvent( + caller_name="play_with_lock", + filename=expected_filename, + linenos=linenos, + lock_name="lock", + task_id=t.ident, + task_name="foobar", + ), + ], + ) + + for f in glob.glob(pprof_prefix + ".*"): + try: + os.remove(f) + except Exception as e: + print("Error removing file: {}".format(e)) + + class TestThreadingLockCollector: # setup_method and teardown_method which will be called before and after # each test method, respectively, part of pytest api. @@ -504,51 +583,6 @@ def test_resource_not_collected(self): ], ) - @pytest.mark.skipif(not TESTING_GEVENT, reason="gevent is not available") - def test_lock_gevent_tasks(self): - from gevent import monkey - - monkey.patch_all() - - def play_with_lock(): - lock = threading.Lock() # !CREATE! test_lock_gevent_tasks - lock.acquire() # !ACQUIRE! test_lock_gevent_tasks - lock.release() # !RELEASE! test_lock_gevent_tasks - - with collector_threading.ThreadingLockCollector(None, capture_pct=100, export_libdd_enabled=True): - t = threading.Thread(name="foobar", target=play_with_lock) - t.start() - t.join() - - ddup.upload() - - linenos = get_lock_linenos("test_lock_gevent_tasks") - - profile = pprof_utils.parse_profile(self.output_filename) - pprof_utils.assert_lock_events( - profile, - expected_acquire_events=[ - pprof_utils.LockAcquireEvent( - caller_name="play_with_lock", - filename=os.path.basename(__file__), - linenos=linenos, - lock_name="lock", - task_id=t.ident, - task_name="foobar", - ), - ], - expected_release_events=[ - pprof_utils.LockReleaseEvent( - caller_name="play_with_lock", - filename=os.path.basename(__file__), - linenos=linenos, - lock_name="lock", - task_id=t.ident, - task_name="foobar", - ), - ], - ) - def test_lock_enter_exit_events(self): with collector_threading.ThreadingLockCollector(None, capture_pct=100, export_libdd_enabled=True): th_lock = threading.Lock() # !CREATE! test_lock_enter_exit_events From 4cbc4edc1df0691784c6c19228cf45cf8639862a Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 16 Oct 2024 00:40:57 -0400 Subject: [PATCH 002/372] chore: update changelog for version 2.14.3 (#10953) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8146b8f4dd2..9f73eff200e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ Changelogs for versions not listed here can be found at https://github.com/DataD --- +## 2.14.3 + + +### Bug Fixes + +- Code Security (IAST) + - Ensures that only the IAST propagation context is cleared instead of all contexts, which could otherwise cause propagation loss in multithreaded applications. Additionally, it improves validations in both the Processor and Vulnerability Reporter, depending on whether IAST is active or not. +- Profiling + - Fixes endpoint profiling for stack v2, when ``DD_PROFILING_STACK_V2_ENABLED`` is set. +- Tracing + - Ensures `DD_TRACE_RATE_LIMIT` environment variable is only applied to spans for which tracer sampling is configured. For spans not matching sampling rules default rate limits should be applied by the Datadog Agent. + ## 2.12.3 ### Bug Fixes From 384654d9253b0ba6a4b3a695076ba190c6812c1e Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:48:14 -0400 Subject: [PATCH 003/372] chore: fix notebook bug with release script (#11035) A few clean up things in the release script. Mainly that the regex to grab the PR number from the commit message broke because it was looking for a 4 digit number, but we recently hit 5 digits. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/release.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/release.py b/scripts/release.py index 9ac5eef691b..0d7344f621d 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -391,7 +391,12 @@ def create_notebook(dd_repo, name, rn, base): author = author.split(" ") author_slack = "@" + author[0] + "." + author[-1] author_dd = author_slack + "@datadoghq.com" - pr_num = re.findall(r"\(#(\d{4})\)", commit.commit.message)[0] + maybe_pr_num = re.findall(r"\(#(\d+)\)", commit.commit.message) + if len(maybe_pr_num) == 0: + print("Could not parse PR number from commit message: ", commit.commit.message) + pr_num = "x" + else: + pr_num = maybe_pr_num[0] url = "https://github.com/DataDog/dd-trace-py/pull/{pr_num}".format(pr_num=pr_num) prs_details.append( {"author_slack": author_slack, "author_dd": author_dd, "url": url, "rn_piece": rn_piece} @@ -402,8 +407,9 @@ def create_notebook(dd_repo, name, rn, base): prs_details = sorted(prs_details, key=lambda prd: prd["rn_piece"]) notebook_rns = "" for pr_detail in prs_details: - notebook_rns += f"\n- [ ] {pr_detail['rn_piece']}\n - PR:{pr_detail['url']}," - "Tester: {pr_detail['author_dd']}\n - Findings: " + notebook_rns += "\n- [ ] {rn_piece}\n - PR:{url}\n - Tester: {author}\n - Findings: ".format( + rn_piece=pr_detail["rn_piece"], url=pr_detail["url"], author=pr_detail["author_dd"] + ) # create the review notebook and add the release notes formatted for testing # get notebook template From 1edbe789980a3a686e1ba597ce8e34ca4759d457 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 16 Oct 2024 19:01:54 +0200 Subject: [PATCH 004/372] chore(iast): refactor iast request context by core.context (#10988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR continues the work Christophe started in PR #10899, focusing on a refactor of the IAST context. Key highlights of this refactor include: - Similar to PR #10899, an `IASTEnvironment` class has been introduced, which contains `request_enabled` and `IastSpanReporter`. These no longer depend on spans, only on the core context. - The IAST context is still created in the Span Processor `on_span_start`. - The context now finalizes with `with core.context_with_data`, and when the WSGI request ends, it calls the listener `context.ended.wsgi.__call__`. Previously, when calling Span Processor `on_span_finish`, the context was sometimes lost. Now, it ensures that the context hasn’t already been reported to avoid this issue, as seen in cases like the Django tests, where `Span Processor.on_span_finish` is called instead of `context.ended.wsgi.__call__`. - The `thread_local struct ThreadContextCache_` has been temporarily removed to align C++ context behavior with Python’s. This is reflected in the test `test_oce_concurrent_requests_futures_in_spans`. That is a rollback of https://github.com/DataDog/dd-trace-py/pull/8452. - The function `_patched_fastapi_function` and Header patching was removed. - Additional validation points have been added for `is_iast_request_enabled` to avoid unnecessary operations. - The logic from `AppSecIastSpanProcessor.is_span_analyzed` has been moved to `is_iast_request_enabled`. - `_asm_request_context.listen` has been renamed to `_asm_request_context.asm_listen` to avoid confusion with other `listen` methods. - `_asm_request_context.in_context` has been renamed to `_asm_request_context.in_asm_context` to differentiate it from `in_iast_context`. - The import of `DDWaf_result` from `ddtrace.appsec._ddwaf` has been moved inside the `if TYPE_CHECKING` block to prevent circular imports caused by this refactor. - IAST wrappers: check if wrapped function has a __func__ to skip `RecursionError: maximum recursion depth exceeded in comparison` in some Python versions https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/668530031 - re.Match is disabled due to segmentation faults and not valid ranges: task APPSEC-55281 Tests: - GRPC tests are failing now task APPSEC-55239 - All IAST tests have been refactored to accommodate the new way of creating and using the context. - The test `test_weak_hash_new_with_child_span` has been removed because we no longer depend on spans or child spans. - The deprecated attribute `oce._enabled = True` has been removed. - An IAST test in `tests/tracer/test_trace_utils.py` had to be updated and was moved to `taint_sinks/test_insecure_cookie.py`, so it requires validation from the @DataDog/apm-sdk-api-python team. - Context creation/finalization has been updated in the benchmarks, requiring validation from the @DataDog/apm-core-python team. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Christophe Papazian Co-authored-by: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> --- benchmarks/appsec_iast_aspects/scenario.py | 22 + .../appsec_iast_propagation/scenario.py | 38 +- benchmarks/bm/utils.py | 5 +- ddtrace/appsec/__init__.py | 6 +- ddtrace/appsec/_asm_request_context.py | 29 +- ddtrace/appsec/_common_module_patches.py | 20 +- ddtrace/appsec/_constants.py | 6 +- ddtrace/appsec/_handlers.py | 21 +- ddtrace/appsec/_iast/_iast_request_context.py | 156 +++++++ .../appsec/_iast/_overhead_control_engine.py | 8 +- ddtrace/appsec/_iast/_patch.py | 43 +- .../appsec/_iast/_patches/json_tainting.py | 7 +- .../Initializer/Initializer.cpp | 2 +- .../appsec/_iast/_taint_tracking/__init__.py | 21 +- .../appsec/_iast/_taint_tracking/aspects.py | 38 +- ddtrace/appsec/_iast/processor.py | 66 +-- ddtrace/appsec/_iast/reporter.py | 18 + ddtrace/appsec/_iast/taint_sinks/_base.py | 54 ++- .../_iast/taint_sinks/command_injection.py | 8 +- .../_iast/taint_sinks/header_injection.py | 6 +- .../_iast/taint_sinks/path_traversal.py | 4 +- ddtrace/appsec/_iast/taint_sinks/ssrf.py | 4 +- .../appsec/_iast/taint_sinks/weak_cipher.py | 24 +- ddtrace/appsec/_iast/taint_sinks/weak_hash.py | 6 + ddtrace/appsec/_processor.py | 4 +- scripts/iast/leak_functions.py | 21 +- scripts/iast/mod_leak_functions.py | 73 +-- tests/appsec/contrib_appsec/utils.py | 1 + tests/appsec/iast/_ast/conftest.py | 15 + tests/appsec/iast/aspects/conftest.py | 17 +- tests/appsec/iast/aspects/test_add_aspect.py | 35 +- .../aspects/test_index_aspect_fixtures.py | 5 + tests/appsec/iast/aspects/test_re_aspects.py | 51 +- tests/appsec/iast/aspects/test_str_aspect.py | 5 - tests/appsec/iast/conftest.py | 159 +++---- .../appsec/iast/fixtures/propagation_path.py | 48 +- ...t_sinks_utils.py => _taint_sinks_utils.py} | 0 tests/appsec/iast/taint_sinks/conftest.py | 12 + .../taint_sinks/test_command_injection.py | 439 ++++++------------ .../test_command_injection_redacted.py | 29 +- .../test_header_injection_redacted.py | 22 +- .../iast/taint_sinks/test_insecure_cookie.py | 75 +-- .../iast/taint_sinks/test_path_traversal.py | 35 +- .../test_path_traversal_redacted.py | 6 +- .../iast/taint_sinks/test_sql_injection.py | 16 +- .../test_sql_injection_redacted.py | 36 +- tests/appsec/iast/taint_sinks/test_ssrf.py | 186 ++++---- .../iast/taint_sinks/test_ssrf_redacted.py | 23 +- .../iast/taint_sinks/test_weak_cipher.py | 97 ++-- .../appsec/iast/taint_sinks/test_weak_hash.py | 146 +++--- .../iast/taint_sinks/test_weak_randomness.py | 23 +- tests/appsec/iast/taint_tracking/conftest.py | 15 + .../taint_tracking/test_native_taint_range.py | 34 +- .../taint_tracking/test_taint_tracking.py | 5 - tests/appsec/iast/test_grpc_iast.py | 31 +- .../appsec/iast/test_iast_propagation_path.py | 27 +- tests/appsec/iast/test_json_tainting.py | 6 +- .../iast/test_overhead_control_engine.py | 52 ++- tests/appsec/iast/test_processor.py | 52 ++- tests/appsec/iast/test_taint_utils.py | 55 +-- tests/appsec/iast/test_telemetry.py | 7 +- .../test_aggregated_memleaks.py | 4 +- tests/appsec/iast_memcheck/conftest.py | 6 +- .../iast_memcheck/test_iast_mem_check.py | 31 +- .../iast_tdd_propagation/flask_orm_app.py | 27 +- .../flask_taint_sinks_views.py | 19 +- .../integrations/pygoat_tests/test_pygoat.py | 8 +- .../integrations/test_flask_iast_patching.py | 2 + tests/appsec/integrations/test_langchain.py | 9 +- tests/appsec/integrations/test_psycopg2.py | 22 +- tests/contrib/dbapi/test_dbapi_appsec.py | 55 ++- .../contrib/django/test_django_appsec_iast.py | 11 +- tests/contrib/flask/test_flask_appsec_iast.py | 32 +- tests/tracer/test_trace_utils.py | 9 - 74 files changed, 1437 insertions(+), 1273 deletions(-) create mode 100644 ddtrace/appsec/_iast/_iast_request_context.py create mode 100644 tests/appsec/iast/_ast/conftest.py rename tests/appsec/iast/taint_sinks/{test_taint_sinks_utils.py => _taint_sinks_utils.py} (100%) create mode 100644 tests/appsec/iast/taint_sinks/conftest.py create mode 100644 tests/appsec/iast/taint_tracking/conftest.py diff --git a/benchmarks/appsec_iast_aspects/scenario.py b/benchmarks/appsec_iast_aspects/scenario.py index 6edd9e78bf3..8a03d1a5c14 100644 --- a/benchmarks/appsec_iast_aspects/scenario.py +++ b/benchmarks/appsec_iast_aspects/scenario.py @@ -6,7 +6,11 @@ import bm from bm.utils import override_env +from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._ast.ast_patching import astpatch_module +from ddtrace.appsec._iast._iast_request_context import end_iast_context +from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled +from ddtrace.appsec._iast._iast_request_context import start_iast_context # Copypasted here from tests.iast.aspects.conftest since the benchmarks can't access tests.* @@ -19,6 +23,18 @@ def _iast_patched_module(module_name): return module_changed +def _start_iast_context_and_oce(): + oce.reconfigure() + oce.acquire_request(None) + start_iast_context() + set_iast_request_enabled(True) + + +def _end_iast_context_and_oce(): + end_iast_context() + oce.release_request() + + class IAST_Aspects(bm.Scenario): iast_enabled: bool mod_original_name: str @@ -27,6 +43,9 @@ class IAST_Aspects(bm.Scenario): def run(self): args = ast.literal_eval(self.args) + if self.iast_enabled: + with override_env({"DD_IAST_ENABLED": "True"}): + _start_iast_context_and_oce() def _(loops): for _ in range(loops): @@ -40,3 +59,6 @@ def _(loops): getattr(module_unpatched, self.function_name)(*args) yield _ + if self.iast_enabled: + with override_env({"DD_IAST_ENABLED": "True"}): + _end_iast_context_and_oce() diff --git a/benchmarks/appsec_iast_propagation/scenario.py b/benchmarks/appsec_iast_propagation/scenario.py index 97f5d613de9..78f0c3dc194 100644 --- a/benchmarks/appsec_iast_propagation/scenario.py +++ b/benchmarks/appsec_iast_propagation/scenario.py @@ -1,18 +1,17 @@ from typing import Any import bm -from bm.utils import override_env - -with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import Source - from ddtrace.appsec._iast._taint_tracking import TaintRange - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import reset_context - from ddtrace.appsec._iast._taint_tracking import set_ranges - from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect - from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import end_iast_context +from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled +from ddtrace.appsec._iast._iast_request_context import start_iast_context +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import Source +from ddtrace.appsec._iast._taint_tracking import TaintRange +from ddtrace.appsec._iast._taint_tracking import set_ranges +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect TAINT_ORIGIN = Source(name="sample_name", value="sample_value", origin=OriginType.PARAMETER) @@ -20,6 +19,18 @@ CHECK_RANGES = [TaintRange(0, 3, TAINT_ORIGIN), TaintRange(21, 3, TAINT_ORIGIN), TaintRange(41, 3, TAINT_ORIGIN)] +def _start_iast_context_and_oce(): + oce.reconfigure() + oce.acquire_request(None) + start_iast_context() + set_iast_request_enabled(True) + + +def _end_iast_context_and_oce(): + end_iast_context() + oce.release_request() + + def taint_pyobject_with_ranges(pyobject: Any, ranges: tuple) -> None: set_ranges(pyobject, tuple(ranges)) @@ -53,8 +64,6 @@ def aspect_function(internal_loop, tainted): def new_request(enable_propagation): tainted = b"my_string".decode("ascii") - reset_context() - create_context() if enable_propagation: taint_pyobject_with_ranges(tainted, (CHECK_RANGES[0],)) @@ -74,6 +83,7 @@ class IastPropagation(bm.Scenario): def run(self): caller_loop = 10 if self.iast_enabled: + _start_iast_context_and_oce() func = aspect_function else: func = normal_function @@ -83,3 +93,5 @@ def _(loops): launch_function(self.iast_enabled, func, self.internal_loop, caller_loop) yield _ + if self.iast_enabled: + _end_iast_context_and_oce() diff --git a/benchmarks/bm/utils.py b/benchmarks/bm/utils.py index 2ce6612bd17..ba6461336b5 100644 --- a/benchmarks/bm/utils.py +++ b/benchmarks/bm/utils.py @@ -71,10 +71,9 @@ def drop_traces(tracer): def drop_telemetry_events(): # Avoids sending instrumentation telemetry payloads to the agent try: - if telemetry.telemetry_writer.is_periodic: - telemetry.telemetry_writer.stop() + telemetry.telemetry_writer.stop() telemetry.telemetry_writer.reset_queues() - telemetry.telemetry_writer.enable(start_worker_thread=False) + telemetry.telemetry_writer.enable() except AttributeError: # telemetry.telemetry_writer is not defined in this version of dd-trace-py # Telemetry events will not be mocked! diff --git a/ddtrace/appsec/__init__.py b/ddtrace/appsec/__init__.py index 2b5f0802c51..6eb05ce2a9d 100644 --- a/ddtrace/appsec/__init__.py +++ b/ddtrace/appsec/__init__.py @@ -3,9 +3,11 @@ def load_appsec(): """Lazily load the appsec module listeners.""" - from ddtrace.appsec._asm_request_context import listen + from ddtrace.appsec._asm_request_context import asm_listen + from ddtrace.appsec._iast._iast_request_context import iast_listen global _APPSEC_TO_BE_LOADED if _APPSEC_TO_BE_LOADED: - listen() + asm_listen() + iast_listen() _APPSEC_TO_BE_LOADED = False diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 11badcdadfe..ea282669f8e 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -2,6 +2,7 @@ import json import re import sys +from typing import TYPE_CHECKING from typing import Any from typing import Callable from typing import Dict @@ -16,8 +17,6 @@ from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import SPAN_DATA_NAMES from ddtrace.appsec._constants import WAF_CONTEXT_NAMES -from ddtrace.appsec._ddwaf import DDWaf_result -from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.appsec._utils import get_triggers from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException @@ -26,6 +25,9 @@ from ddtrace.settings.asm import config as asm_config +if TYPE_CHECKING: + from ddtrace.appsec._ddwaf import DDWaf_result + log = get_logger(__name__) # Stopgap module for providing ASM context for the blocking features wrapping some contextvars. @@ -56,7 +58,7 @@ class ASM_Environment: """ def __init__(self, span: Optional[Span] = None): - self.root = not in_context() + self.root = not in_asm_context() if self.root: core.add_suppress_exception(BlockingException) if span is None: @@ -92,7 +94,7 @@ def _get_asm_context() -> Optional[ASM_Environment]: return core.get_item(_ASM_CONTEXT) -def in_context() -> bool: +def in_asm_context() -> bool: return core.get_item(_ASM_CONTEXT) is not None @@ -293,7 +295,7 @@ def set_waf_callback(value) -> None: set_value(_CALLBACKS, _WAF_CALL, value) -def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> Optional[DDWaf_result]: +def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> Optional["DDWaf_result"]: if not asm_config._asm_enabled: return None callback = get_value(_CALLBACKS, _WAF_CALL) @@ -451,7 +453,7 @@ def _on_context_ended(ctx): def _on_wrapped_view(kwargs): return_value = [None, None] # if Appsec is enabled, we can try to block as we have the path parameters at that point - if asm_config._asm_enabled and in_context(): + if asm_config._asm_enabled and in_asm_context(): log.debug("Flask WAF call for Suspicious Request Blocking on request") if kwargs: set_waf_address(REQUEST_PATH_PARAMS, kwargs) @@ -461,12 +463,14 @@ def _on_wrapped_view(kwargs): return_value[0] = callback_block # If IAST is enabled, taint the Flask function kwargs (path parameters) + from ddtrace.appsec._iast._utils import _is_iast_enabled + if _is_iast_enabled() and kwargs: + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject - from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor - if not AppSecIastSpanProcessor.is_span_analyzed(): + if not is_iast_request_enabled(): return return_value _kwargs = {} @@ -479,16 +483,18 @@ def _on_wrapped_view(kwargs): def _on_set_request_tags(request, span, flask_config): + from ddtrace.appsec._iast._utils import _is_iast_enabled + if _is_iast_enabled(): + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_utils import taint_structure - from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) _set_metric_iast_instrumented_source(OriginType.COOKIE) - if not AppSecIastSpanProcessor.is_span_analyzed(span._local_root or span): + if not is_iast_request_enabled(): return request.cookies = taint_structure( @@ -556,10 +562,11 @@ def _get_headers_if_appsec(): return get_headers() -def listen(): +def asm_listen(): from ddtrace.appsec._handlers import listen listen() + core.on("flask.finalize_request.post", _set_headers_and_response) core.on("flask.wrapped_view", _on_wrapped_view, "callback_and_args") core.on("flask._patched_request", _on_pre_tracedrequest) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 2660617dc78..c41350fd2ad 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -81,7 +81,7 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs ): try: from ddtrace.appsec._asm_request_context import call_waf_callback - from ddtrace.appsec._asm_request_context import in_context + from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -93,7 +93,7 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs filename = os.fspath(filename_arg) except Exception: filename = "" - if filename and in_context(): + if filename and in_asm_context(): res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.LFI: filename}, crop_trace="wrapped_open_CFDDB7ABBA9081B6", @@ -126,7 +126,7 @@ def wrapped_open_ED4CF71136E15EBF(original_open_callable, instance, args, kwargs ): try: from ddtrace.appsec._asm_request_context import call_waf_callback - from ddtrace.appsec._asm_request_context import in_context + from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -134,7 +134,7 @@ def wrapped_open_ED4CF71136E15EBF(original_open_callable, instance, args, kwargs return original_open_callable(*args, **kwargs) url = args[0] if args else kwargs.get("fullurl", None) - if url and in_context(): + if url and in_asm_context(): if url.__class__.__name__ == "Request": url = url.get_full_url() if isinstance(url, str): @@ -166,7 +166,7 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, ): try: from ddtrace.appsec._asm_request_context import call_waf_callback - from ddtrace.appsec._asm_request_context import in_context + from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -174,7 +174,7 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, return original_request_callable(*args, **kwargs) url = args[1] if len(args) > 1 else kwargs.get("url", None) - if url and in_context(): + if url and in_asm_context(): if isinstance(url, str): res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.SSRF: url}, @@ -206,12 +206,12 @@ def wrapped_system_5542593D237084A7(original_command_callable, instance, args, k ): try: from ddtrace.appsec._asm_request_context import call_waf_callback - from ddtrace.appsec._asm_request_context import in_context + from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: return original_command_callable(*args, **kwargs) - if in_context(): + if in_asm_context(): res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.CMDI: command}, crop_trace="wrapped_system_5542593D237084A7", @@ -254,14 +254,14 @@ def execute_4C9BAC8E228EB347(instrument_self, query, args, kwargs) -> None: ): try: from ddtrace.appsec._asm_request_context import call_waf_callback - from ddtrace.appsec._asm_request_context import in_context + from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # execute is used during module initialization # and shouldn't be changed at that time return - if instrument_self and query and in_context(): + if instrument_self and query and in_asm_context(): db_type = _DB_DIALECTS.get( getattr(instrument_self, "_self_config", {}).get("_dbapi_span_name_prefix", ""), "" ) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index a585cc48411..5b48dfaa181 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -1,5 +1,4 @@ import os -from re import Match import sys from _io import BytesIO @@ -120,13 +119,12 @@ class IAST(metaclass=Constant_Class): LAZY_TAINT: Literal["_DD_IAST_LAZY_TAINT"] = "_DD_IAST_LAZY_TAINT" JSON: Literal["_dd.iast.json"] = "_dd.iast.json" ENABLED: Literal["_dd.iast.enabled"] = "_dd.iast.enabled" - CONTEXT_KEY: Literal["_iast_data"] = "_iast_data" PATCH_MODULES: Literal["_DD_IAST_PATCH_MODULES"] = "_DD_IAST_PATCH_MODULES" DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES" SEP_MODULES: Literal[","] = "," - REQUEST_IAST_ENABLED: Literal["_dd.iast.request_enabled"] = "_dd.iast.request_enabled" TEXT_TYPES = (str, bytes, bytearray) - TAINTEABLE_TYPES = (str, bytes, bytearray, Match, BytesIO, StringIO) + # TODO(avara1986): `Match` contains errors. APPSEC-55239 + TAINTEABLE_TYPES = (str, bytes, bytearray, BytesIO, StringIO) class IAST_SPAN_TAGS(metaclass=Constant_Class): diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index a815edaf360..fde10f441de 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -9,6 +9,7 @@ from ddtrace.appsec._asm_request_context import get_blocked from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._iast._iast_request_context import in_iast_context from ddtrace.appsec._iast._patch import if_iast_taint_returned_object_for from ddtrace.appsec._iast._patch import if_iast_taint_yield_tuple_for from ddtrace.appsec._iast._utils import _is_iast_enabled @@ -194,15 +195,11 @@ def _on_request_span_modifier( def _on_request_init(wrapped, instance, args, kwargs): wrapped(*args, **kwargs) - if _is_iast_enabled(): + if _is_iast_enabled() and in_iast_context(): try: from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import taint_pyobject - from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor - - if not AppSecIastSpanProcessor.is_span_analyzed(): - return instance.query_string = taint_pyobject( pyobject=instance.query_string, @@ -290,9 +287,8 @@ def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_utils import taint_structure - from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor - if not AppSecIastSpanProcessor.is_span_analyzed(): + if not in_iast_context(): return http_req = fn_args[0] @@ -357,16 +353,9 @@ def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): def _on_wsgi_environ(wrapped, _instance, args, kwargs): - if _is_iast_enabled(): - if not args: - return wrapped(*args, **kwargs) - - from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 + if _is_iast_enabled() and args and in_iast_context(): + from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_utils import taint_structure - from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor - - if not AppSecIastSpanProcessor.is_span_analyzed(): - return wrapped(*args, **kwargs) return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py new file mode 100644 index 00000000000..462a2975269 --- /dev/null +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -0,0 +1,156 @@ +import sys +from typing import Optional + +from ddtrace._trace.span import Span +from ddtrace.appsec._constants import APPSEC +from ddtrace.appsec._constants import IAST +from ddtrace.appsec._iast import _is_iast_enabled +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._metrics import _set_metric_iast_request_tainted +from ddtrace.appsec._iast._metrics import _set_span_tag_iast_executed_sink +from ddtrace.appsec._iast._metrics import _set_span_tag_iast_request_tainted +from ddtrace.appsec._iast.reporter import IastSpanReporter +from ddtrace.appsec._trace_utils import _asm_manual_keep +from ddtrace.constants import ORIGIN_KEY +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + +# Stopgap module for providing ASM context for the blocking features wrapping some contextvars. + +if sys.version_info >= (3, 8): + from typing import Literal # noqa:F401 +else: + from typing_extensions import Literal # noqa:F401 + +_IAST_CONTEXT: Literal["_iast_env"] = "_iast_env" + + +class IASTEnvironment: + """ + an object of this class contains all asm data (waf and telemetry) + for a single request. It is bound to a single asm request context. + It is contained into a ContextVar. + """ + + def __init__(self, span: Optional[Span] = None): + if span is None: + self.span: Span = core.get_item(core.get_item("call_key")) + + self.request_enabled: bool = False + self.iast_reporter: Optional[IastSpanReporter] = None + + +def _get_iast_context() -> Optional[IASTEnvironment]: + return core.get_item(_IAST_CONTEXT) + + +def in_iast_context() -> bool: + return core.get_item(_IAST_CONTEXT) is not None + + +def start_iast_context(): + if _is_iast_enabled(): + from ._taint_tracking import create_context as create_propagation_context + + create_propagation_context() + core.set_item(_IAST_CONTEXT, IASTEnvironment()) + + +def end_iast_context(span: Optional[Span] = None): + from ._taint_tracking import reset_context as reset_propagation_context + + env = _get_iast_context() + if env is not None and env.span is span: + finalize_iast_env(env) + reset_propagation_context() + + +def finalize_iast_env(env: IASTEnvironment) -> None: + core.discard_item(_IAST_CONTEXT) + + +def set_iast_reporter(iast_reporter: IastSpanReporter) -> None: + env = _get_iast_context() + if env: + env.iast_reporter = iast_reporter + else: + log.debug("[IAST] Trying to set IAST reporter but no context is present") + + +def get_iast_reporter() -> Optional[IastSpanReporter]: + env = _get_iast_context() + if env: + return env.iast_reporter + return None + + +def set_iast_request_enabled(request_enabled) -> None: + env = _get_iast_context() + if env: + env.request_enabled = request_enabled + else: + log.debug("[IAST] Trying to set IAST reporter but no context is present") + + +def is_iast_request_enabled(): + env = _get_iast_context() + if env: + return env.request_enabled + return False + + +def _iast_end_request(ctx=None, span=None, *args, **kwargs): + try: + if _is_iast_enabled(): + if span: + req_span = span + else: + req_span = ctx.get_item("req_span") + exist_data = req_span.get_tag(IAST.JSON) + if not exist_data: + if not is_iast_request_enabled(): + req_span.set_metric(IAST.ENABLED, 0.0) + end_iast_context(req_span) + oce.release_request() + return + + req_span.set_metric(IAST.ENABLED, 1.0) + report_data: Optional[IastSpanReporter] = get_iast_reporter() + + if report_data: + report_data.build_and_scrub_value_parts() + req_span.set_tag_str(IAST.JSON, report_data._to_str()) + _asm_manual_keep(req_span) + _set_metric_iast_request_tainted() + _set_span_tag_iast_request_tainted(req_span) + _set_span_tag_iast_executed_sink(req_span) + + set_iast_request_enabled(False) + end_iast_context(req_span) + + if req_span.get_tag(ORIGIN_KEY) is None: + req_span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + + oce.release_request() + except Exception: + log.debug("[IAST] Error finishing IAST context", exc_info=True) + + +def _iast_start_request(span=None, *args, **kwargs): + try: + if _is_iast_enabled(): + start_iast_context() + request_iast_enabled = False + if oce.acquire_request(span): + request_iast_enabled = True + set_iast_request_enabled(request_iast_enabled) + except Exception: + log.debug("[IAST] Error starting IAST context", exc_info=True) + + +def iast_listen(): + core.on("context.ended.wsgi.__call__", _iast_end_request) + core.on("context.ended.asgi.__call__", _iast_end_request) diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index d3e9503047b..e9180c53b3e 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -10,6 +10,7 @@ from typing import Type from ddtrace._trace.span import Span +from ddtrace.appsec._iast._utils import _is_iast_debug_enabled from ddtrace.internal._unpatched import _threading as threading from ddtrace.internal.logger import get_logger from ddtrace.sampler import RateSampler @@ -96,7 +97,12 @@ def acquire_request(self, span: Span) -> bool: - Block a request's quota at start of the request to limit simultaneous requests analyzed. - Use sample rating to analyze only a percentage of the total requests (30% by default). """ - if self._request_quota <= 0 or not self._sampler.sample(span): + if self._request_quota <= 0: + return False + + if span and not self._sampler.sample(span): + if _is_iast_debug_enabled(): + log.debug("[IAST] Skip request by sampling rate") return False with self._lock: diff --git a/ddtrace/appsec/_iast/_patch.py b/ddtrace/appsec/_iast/_patch.py index 569b3d1b2f5..ea58bae9856 100644 --- a/ddtrace/appsec/_iast/_patch.py +++ b/ddtrace/appsec/_iast/_patch.py @@ -8,6 +8,7 @@ from ddtrace.appsec._common_module_patches import wrap_object from ddtrace.internal.logger import get_logger +from ._iast_request_context import is_iast_request_enabled from ._metrics import _set_metric_iast_instrumented_source from ._taint_utils import taint_structure from ._utils import _is_iast_enabled @@ -47,14 +48,10 @@ def try_wrap_function_wrapper(module: Text, name: Text, wrapper: Callable): def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): value = wrapped(*args, **kwargs) - if _is_iast_enabled(): + if _is_iast_enabled() and is_iast_request_enabled(): try: from ._taint_tracking import is_pyobject_tainted from ._taint_tracking import taint_pyobject - from .processor import AppSecIastSpanProcessor - - if not AppSecIastSpanProcessor.is_span_analyzed(): - return value if not is_pyobject_tainted(value): name = str(args[0]) if len(args) else "http.request.body" @@ -71,9 +68,8 @@ def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): if _is_iast_enabled(): from ._taint_tracking import taint_pyobject - from .processor import AppSecIastSpanProcessor - if not AppSecIastSpanProcessor.is_span_analyzed(): + if not is_iast_request_enabled(): for key, value in wrapped(*args, **kwargs): yield key, value else: @@ -95,29 +91,6 @@ def _patched_dictionary(origin_key, origin_value, original_func, instance, args, return taint_structure(result, origin_key, origin_value, override_pyobject_tainted=True) -def _patched_fastapi_function(origin, original_func, instance, args, kwargs): - result = original_func(*args, **kwargs) - - if _is_iast_enabled(): - try: - from ._taint_tracking import is_pyobject_tainted - from .processor import AppSecIastSpanProcessor - - if not AppSecIastSpanProcessor.is_span_analyzed(): - return result - - if not is_pyobject_tainted(result): - from ._taint_tracking import origin_to_str - from ._taint_tracking import taint_pyobject - - return taint_pyobject( - pyobject=result, source_name=origin_to_str(origin), source_value=result, source_origin=origin - ) - except Exception: - log.debug("Unexpected exception while tainting pyobject", exc_info=True) - return result - - def _on_iast_fastapi_patch(): from ddtrace.appsec._iast._taint_tracking import OriginType @@ -127,11 +100,6 @@ def _on_iast_fastapi_patch(): "cookie_parser", functools.partial(_patched_dictionary, OriginType.COOKIE_NAME, OriginType.COOKIE), ) - try_wrap_function_wrapper( - "fastapi", - "Cookie", - functools.partial(_patched_fastapi_function, OriginType.COOKIE_NAME), - ) _set_metric_iast_instrumented_source(OriginType.COOKIE) _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) @@ -159,11 +127,6 @@ def _on_iast_fastapi_patch(): "Headers.get", functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), ) - try_wrap_function_wrapper( - "fastapi", - "Header", - functools.partial(_patched_fastapi_function, OriginType.HEADER), - ) _set_metric_iast_instrumented_source(OriginType.HEADER) # Path source diff --git a/ddtrace/appsec/_iast/_patches/json_tainting.py b/ddtrace/appsec/_iast/_patches/json_tainting.py index 7bd297a492a..28cfe41e592 100644 --- a/ddtrace/appsec/_iast/_patches/json_tainting.py +++ b/ddtrace/appsec/_iast/_patches/json_tainting.py @@ -4,6 +4,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config +from .._iast_request_context import is_iast_request_enabled from .._patch import set_and_check_module_is_patched from .._patch import set_module_unpatched from .._patch import try_wrap_function_wrapper @@ -41,13 +42,9 @@ def wrapped_loads(wrapped, instance, args, kwargs): from .._taint_utils import taint_structure obj = wrapped(*args, **kwargs) - if asm_config._iast_enabled: + if asm_config._iast_enabled and is_iast_request_enabled(): from .._taint_tracking import get_tainted_ranges from .._taint_tracking import taint_pyobject - from ..processor import AppSecIastSpanProcessor - - if not AppSecIastSpanProcessor.is_span_analyzed(): - return obj ranges = get_tainted_ranges(args[0]) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp index 657d8a92e10..58563cb680e 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp @@ -5,7 +5,7 @@ using namespace std; using namespace pybind11::literals; -thread_local struct ThreadContextCache_ +struct ThreadContextCache_ { TaintRangeMapTypePtr tx_map = nullptr; } ThreadContextCache; diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 89603d0711a..e7bca86d128 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -7,6 +7,7 @@ from ddtrace.internal.logger import get_logger from ..._constants import IAST +from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_iast_error_metric from .._metrics import _set_metric_iast_executed_source from .._utils import _is_iast_debug_enabled @@ -127,7 +128,9 @@ def iast_taint_log_error(msg): def is_pyobject_tainted(pyobject: Any) -> bool: - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + if not is_iast_request_enabled(): + return False + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): return False try: @@ -138,8 +141,10 @@ def is_pyobject_tainted(pyobject: Any) -> bool: def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: - # Pyobject must be Text with len > 1 - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + if not is_iast_request_enabled(): + return pyobject + + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): return pyobject # We need this validation in different contition if pyobject is not a text type and creates a side-effect such as # __len__ magic method call. @@ -164,12 +169,14 @@ def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_or _set_metric_iast_executed_source(source_origin) return pyobject_newid except ValueError as e: - log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e) + log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e, exc_info=True) return pyobject def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + if not is_iast_request_enabled(): + return False + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): return False try: set_ranges(pyobject, ranges) @@ -180,7 +187,9 @@ def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: def get_tainted_ranges(pyobject: Any) -> Tuple: - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + if not is_iast_request_enabled(): + return tuple() + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): return tuple() try: return get_ranges(pyobject) diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index f3d2de34392..4f0679387c8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -977,27 +977,29 @@ def re_finditer_aspect(orig_function: Optional[Callable], flag_added_args: int, self = args[0] args = args[(flag_added_args or 1) :] result = orig_function(*args, **kwargs) - - if not isinstance(self, (Pattern, ModuleType)): - # This is not the sub we're looking for - return result - elif isinstance(self, ModuleType): - if self.__name__ != "re" or self.__package__ not in ("", "re"): + try: + if not isinstance(self, (Pattern, ModuleType)): + # This is not the sub we're looking for return result - # In this case, the first argument is the pattern - # which we don't need to check for tainted ranges - args = args[1:] + elif isinstance(self, ModuleType): + if self.__name__ != "re" or self.__package__ not in ("", "re"): + return result + # In this case, the first argument is the pattern + # which we don't need to check for tainted ranges + args = args[1:] - elif not isinstance(result, Iterator): - return result + elif not isinstance(result, Iterator): + return result - if len(args) >= 1: - string = args[0] - if is_pyobject_tainted(string): - ranges = get_ranges(string) - result, result_backup = itertools.tee(result) - for elem in result_backup: - taint_pyobject_with_ranges(elem, ranges) + if len(args) >= 1: + string = args[0] + if is_pyobject_tainted(string): + ranges = get_ranges(string) + result, result_backup = itertools.tee(result) + for elem in result_backup: + taint_pyobject_with_ranges(elem, ranges) + except Exception as e: + iast_taint_log_error("IAST propagation error. re_finditer_aspect. {}".format(e)) return result diff --git a/ddtrace/appsec/_iast/processor.py b/ddtrace/appsec/_iast/processor.py index 0a04e8b2be1..2eb7d64243b 100644 --- a/ddtrace/appsec/_iast/processor.py +++ b/ddtrace/appsec/_iast/processor.py @@ -1,22 +1,13 @@ from dataclasses import dataclass -from typing import Optional from ddtrace._trace.processor import SpanProcessor from ddtrace._trace.span import Span from ddtrace.appsec import load_appsec -from ddtrace.appsec._constants import APPSEC -from ddtrace.appsec._constants import IAST -from ddtrace.constants import ORIGIN_KEY from ddtrace.ext import SpanTypes -from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from .._trace_utils import _asm_manual_keep -from . import oce -from ._metrics import _set_metric_iast_request_tainted -from ._metrics import _set_span_tag_iast_executed_sink -from ._metrics import _set_span_tag_iast_request_tainted -from .reporter import IastSpanReporter +from ._iast_request_context import _iast_end_request +from ._iast_request_context import _iast_start_request log = get_logger(__name__) @@ -24,30 +15,16 @@ @dataclass(eq=False) class AppSecIastSpanProcessor(SpanProcessor): - @staticmethod - def is_span_analyzed(span: Optional[Span] = None) -> bool: - if span is None: - from ddtrace import tracer + def __post_init__(self) -> None: + from ddtrace.appsec import load_appsec - span = tracer.current_root_span() - - if span and span.span_type == SpanTypes.WEB and core.get_item(IAST.REQUEST_IAST_ENABLED, span=span): - return True - return False + load_appsec() def on_span_start(self, span: Span): - if span.span_type not in {SpanTypes.WEB, SpanTypes.GRPC}: + if span.span_type != SpanTypes.WEB: return - from ._taint_tracking import create_context - - create_context() - - request_iast_enabled = False - if oce.acquire_request(span): - request_iast_enabled = True - - core.set_item(IAST.REQUEST_IAST_ENABLED, request_iast_enabled, span=span) + _iast_start_request(span) def on_span_finish(self, span: Span): """Report reported vulnerabilities. @@ -57,34 +34,9 @@ def on_span_finish(self, span: Span): - `_dd.iast.enabled`: Set to 1 when IAST is enabled in a request. If a request is disabled (e.g. by sampling), then it is not set. """ - if span.span_type not in {SpanTypes.WEB, SpanTypes.GRPC}: - return - - from ._taint_tracking import reset_context - - if not core.get_item(IAST.REQUEST_IAST_ENABLED, span=span): - span.set_metric(IAST.ENABLED, 0.0) - reset_context() + if span.span_type != SpanTypes.WEB: return - - span.set_metric(IAST.ENABLED, 1.0) - - report_data: IastSpanReporter = core.get_item(IAST.CONTEXT_KEY, span=span) - - if report_data: - report_data.build_and_scrub_value_parts() - span.set_tag_str(IAST.JSON, report_data._to_str()) - _asm_manual_keep(span) - - _set_metric_iast_request_tainted() - _set_span_tag_iast_request_tainted(span) - _set_span_tag_iast_executed_sink(span) - reset_context() - - if span.get_tag(ORIGIN_KEY) is None: - span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) - - oce.release_request() + _iast_end_request(span=span) load_appsec() diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index f0f5c20571c..249d8e21278 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -16,8 +16,11 @@ from ddtrace.appsec._iast.constants import VULN_INSECURE_HASHING_TYPE from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE from ddtrace.appsec._iast.constants import VULN_WEAK_RANDOMNESS +from ddtrace.internal.logger import get_logger +log = get_logger(__name__) + ATTRS_TO_SKIP = frozenset({"_ranges", "_evidences_with_no_sources", "dialect"}) EVIDENCES_WITH_NO_SOURCES = [VULN_INSECURE_HASHING_TYPE, VULN_WEAK_CIPHER_TYPE, VULN_WEAK_RANDOMNESS] @@ -154,6 +157,14 @@ def taint_ranges_as_evidence_info(pyobject: Any) -> Tuple[List[Source], List[Dic return sources, tainted_ranges_to_dict def add_ranges_to_evidence_and_extract_sources(self, vuln): + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + + if not is_iast_request_enabled(): + log.debug( + "[IAST] add_ranges_to_evidence_and_extract_sources. " + "No request quota or this vulnerability is outside the context" + ) + return sources, tainted_ranges_to_dict = self.taint_ranges_as_evidence_info(vuln.evidence.value) vuln.evidence._ranges = tainted_ranges_to_dict for source in sources: @@ -167,6 +178,13 @@ def build_and_scrub_value_parts(self) -> Dict[str, Any]: Returns: - Dict[str, Any]: Dictionary representation of the IAST span reporter. """ + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + + if not is_iast_request_enabled(): + log.debug( + "[IAST] build_and_scrub_value_parts. No request quota or this vulnerability is outside the context" + ) + return {} for vuln in self.vulnerabilities: scrubbing_result = sensitive_handler.scrub_evidence( vuln.type, vuln.evidence, vuln.evidence._ranges, self.sources diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 50e025f393c..3aac2a4b170 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -5,15 +5,16 @@ from typing import Text from ddtrace import tracer -from ddtrace.appsec._constants import IAST -from ddtrace.internal import core from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.cache import LFUCache from ..._deduplications import deduplication +from .._iast_request_context import get_iast_reporter +from .._iast_request_context import is_iast_request_enabled +from .._iast_request_context import set_iast_reporter from .._overhead_control_engine import Operation from .._stacktrace import get_info_frame -from ..processor import AppSecIastSpanProcessor +from .._utils import _is_iast_debug_enabled from ..reporter import Evidence from ..reporter import IastSpanReporter from ..reporter import Location @@ -58,25 +59,42 @@ def wrapper(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: """Get the current root Span and attach it to the wrapped function. We need the span to report the vulnerability and update the context with the report information. """ - if AppSecIastSpanProcessor.is_span_analyzed() and cls.has_quota(): + if not is_iast_request_enabled(): + log.debug( + "[IAST] VulnerabilityBase.wrapper. No request quota or this vulnerability is outside the context" + ) + return wrapped(*args, **kwargs) + elif cls.has_quota(): return func(wrapped, instance, args, kwargs) else: - log.debug("IAST: no vulnerability quota to analyze more sink points") - return wrapped(*args, **kwargs) + return wrapped(*args, **kwargs) return wrapper @classmethod @taint_sink_deduplication - def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_number): + def _prepare_report(cls, vulnerability_type, evidence, file_name, line_number): + if not is_iast_request_enabled(): + if _is_iast_debug_enabled(): + log.debug( + "[IAST] VulnerabilityBase._prepare_report. " + "No request quota or this vulnerability is outside the context" + ) + return False if line_number is not None and (line_number == 0 or line_number < -1): line_number = -1 - report = core.get_item(IAST.CONTEXT_KEY, span=span) + report = get_iast_reporter() + span_id = 0 + if tracer and hasattr(tracer, "current_root_span"): + span = tracer.current_root_span() + if span: + span_id = span.span_id + vulnerability = Vulnerability( type=vulnerability_type, evidence=evidence, - location=Location(path=file_name, line=line_number, spanId=span.span_id), + location=Location(path=file_name, line=line_number, spanId=span_id), ) if report: report.vulnerabilities.add(vulnerability) @@ -84,7 +102,7 @@ def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_num report = IastSpanReporter(vulnerabilities={vulnerability}) report.add_ranges_to_evidence_and_extract_sources(vulnerability) - core.set_item(IAST.CONTEXT_KEY, report, span=span) + set_iast_reporter(report) return True @@ -93,20 +111,6 @@ def report(cls, evidence_value: Text = "", dialect: Optional[Text] = None) -> No """Build a IastSpanReporter instance to report it in the `AppSecIastSpanProcessor` as a string JSON""" # TODO: type of evidence_value will be Text. We wait to finish the redaction refactor. if cls.acquire_quota(): - if not tracer or not hasattr(tracer, "current_root_span"): - log.debug( - "[IAST] VulnerabilityReporter is trying to report an evidence, " - "but not tracer or tracer has no root span" - ) - return None - - span = tracer.current_root_span() - if not span: - log.debug( - "[IAST] VulnerabilityReporter. No root span in the current execution. Skipping IAST taint sink." - ) - return None - file_name = None line_number = None @@ -131,7 +135,7 @@ def report(cls, evidence_value: Text = "", dialect: Optional[Text] = None) -> No log.debug("Unexpected evidence_value type: %s", type(evidence_value)) evidence = Evidence(value="", dialect=dialect) - result = cls._prepare_report(span, cls.vulnerability_type, evidence, file_name, line_number) + result = cls._prepare_report(cls.vulnerability_type, evidence, file_name, line_number) # If result is None that's mean deduplication raises and no vulnerability wasn't reported, with that, # we need to restore the quota if not result: diff --git a/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/ddtrace/appsec/_iast/taint_sinks/command_injection.py index 1985de08dd6..0cfd48a5816 100644 --- a/ddtrace/appsec/_iast/taint_sinks/command_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -9,11 +9,11 @@ from ..._common_module_patches import try_unwrap from ..._constants import IAST_SPAN_TAGS from .. import oce +from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_metric_iast_instrumented_sink from .._metrics import increment_iast_span_metric from .._patch import try_wrap_function_wrapper from ..constants import VULN_CMDI -from ..processor import AppSecIastSpanProcessor from ._base import VulnerabilityBase @@ -54,6 +54,8 @@ def _iast_cmdi_osspawn(wrapped, instance, args, kwargs): mode, file, func_args, _, _ = args _iast_report_cmdi(func_args) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -61,6 +63,8 @@ def _iast_cmdi_subprocess_init(wrapped, instance, args, kwargs): cmd_args = args[0] if len(args) else kwargs["args"] _iast_report_cmdi(cmd_args) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -76,7 +80,7 @@ def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CommandInjection.vulnerability_type) _set_metric_iast_executed_sink(CommandInjection.vulnerability_type) - if AppSecIastSpanProcessor.is_span_analyzed() and CommandInjection.has_quota(): + if is_iast_request_enabled() and CommandInjection.has_quota(): from .._taint_tracking import is_pyobject_tainted from .._taint_tracking.aspects import join_aspect diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 6a3ac323d6e..cc091bb6ae1 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -8,6 +8,7 @@ from ..._common_module_patches import try_unwrap from ..._constants import IAST_SPAN_TAGS from .. import oce +from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_metric_iast_instrumented_sink from .._metrics import increment_iast_span_metric from .._patch import set_and_check_module_is_patched @@ -15,7 +16,6 @@ from .._patch import try_wrap_function_wrapper from ..constants import HEADER_NAME_VALUE_SEPARATOR from ..constants import VULN_HEADER_INJECTION -from ..processor import AppSecIastSpanProcessor from ._base import VulnerabilityBase @@ -71,6 +71,8 @@ def unpatch(): def _iast_h(wrapped, instance, args, kwargs): if asm_config._iast_enabled: _iast_report_header_injection(args) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -101,7 +103,7 @@ def _iast_report_header_injection(headers_args) -> None: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, HeaderInjection.vulnerability_type) _set_metric_iast_executed_sink(HeaderInjection.vulnerability_type) - if AppSecIastSpanProcessor.is_span_analyzed() and HeaderInjection.has_quota(): + if is_iast_request_enabled() and HeaderInjection.has_quota(): if is_pyobject_tainted(header_name) or is_pyobject_tainted(header_value): header_evidence = add_aspect(add_aspect(header_name, HEADER_NAME_VALUE_SEPARATOR), header_value) HeaderInjection.report(evidence_value=header_evidence) diff --git a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py index d691c9c19f3..1fd9cff8956 100644 --- a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py +++ b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py @@ -4,9 +4,9 @@ from ..._constants import IAST_SPAN_TAGS from .. import oce +from .._iast_request_context import is_iast_request_enabled from .._metrics import increment_iast_span_metric from ..constants import VULN_PATH_TRAVERSAL -from ..processor import AppSecIastSpanProcessor from ._base import VulnerabilityBase @@ -19,7 +19,7 @@ class PathTraversal(VulnerabilityBase): def check_and_report_path_traversal(*args: Any, **kwargs: Any) -> None: - if AppSecIastSpanProcessor.is_span_analyzed() and PathTraversal.has_quota(): + if is_iast_request_enabled() and PathTraversal.has_quota(): try: from .._metrics import _set_metric_iast_executed_sink from .._taint_tracking import is_pyobject_tainted diff --git a/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/ddtrace/appsec/_iast/taint_sinks/ssrf.py index 47e51387c60..7233aa54cec 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ssrf.py +++ b/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -7,9 +7,9 @@ from ..._constants import IAST_SPAN_TAGS from .. import oce +from .._iast_request_context import is_iast_request_enabled from .._metrics import increment_iast_span_metric from ..constants import VULN_SSRF -from ..processor import AppSecIastSpanProcessor from ._base import VulnerabilityBase @@ -50,7 +50,7 @@ def _iast_report_ssrf(func: Callable, *args, **kwargs): _set_metric_iast_executed_sink(SSRF.vulnerability_type) increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SSRF.vulnerability_type) - if AppSecIastSpanProcessor.is_span_analyzed() and SSRF.has_quota(): + if is_iast_request_enabled() and SSRF.has_quota(): try: from .._taint_tracking import is_pyobject_tainted diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py index f57a4dd3cf1..728d0c1d932 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py @@ -103,19 +103,28 @@ def patch(): def wrapped_aux_rc2_function(wrapped, instance, args, kwargs): - result = wrapped(*args, **kwargs) + if hasattr(wrapped, "__func__"): + result = wrapped.__func__(instance, *args, **kwargs) + else: + result = wrapped(*args, **kwargs) result._dd_weakcipher_algorithm = "RC2" return result def wrapped_aux_des_function(wrapped, instance, args, kwargs): - result = wrapped(*args, **kwargs) + if hasattr(wrapped, "__func__"): + result = wrapped.__func__(instance, *args, **kwargs) + else: + result = wrapped(*args, **kwargs) result._dd_weakcipher_algorithm = "DES" return result def wrapped_aux_blowfish_function(wrapped, instance, args, kwargs): - result = wrapped(*args, **kwargs) + if hasattr(wrapped, "__func__"): + result = wrapped.__func__(instance, *args, **kwargs) + else: + result = wrapped(*args, **kwargs) result._dd_weakcipher_algorithm = "Blowfish" return result @@ -127,6 +136,8 @@ def wrapped_rc4_function(wrapped: Callable, instance: Any, args: Any, kwargs: An WeakCipher.report( evidence_value="RC4", ) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -139,7 +150,10 @@ def wrapped_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) - WeakCipher.report( evidence_value=evidence, ) - + print("@WeakCipher.wrap.wrapped_function!!!!!!!!!!!!!!!!!") + print(wrapped) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -152,4 +166,6 @@ def wrapped_cryptography_function(wrapped: Callable, instance: Any, args: Any, k WeakCipher.report( evidence_value=algorithm_name, ) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py index e5ead3aed29..4a4e2c2392e 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py @@ -126,6 +126,8 @@ def wrapped_digest_function(wrapped: Callable, instance: Any, args: Any, kwargs: WeakHash.report( evidence_value=instance.name, ) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -147,6 +149,8 @@ def wrapped_new_function(wrapped: Callable, instance: Any, args: Any, kwargs: An WeakHash.report( evidence_value=args[0].lower(), ) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) @@ -156,4 +160,6 @@ def wrapped_function(wrapped: Callable, evidence: Text, instance: Any, args: Any WeakHash.report( evidence_value=evidence, ) + if hasattr(wrapped, "__func__"): + return wrapped.__func__(instance, *args, **kwargs) return wrapped(*args, **kwargs) diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index ab7a74ee99b..0cdf1003e2a 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -17,7 +17,6 @@ from ddtrace._trace.processor import SpanProcessor from ddtrace._trace.span import Span from ddtrace.appsec import _asm_request_context -from ddtrace.appsec import load_appsec from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import DEFAULT from ddtrace.appsec._constants import EXPLOIT_PREVENTION @@ -141,6 +140,7 @@ def enabled(self): return self._ddwaf is not None def __post_init__(self) -> None: + from ddtrace.appsec import load_appsec from ddtrace.appsec._ddwaf import DDWaf load_appsec() @@ -430,7 +430,7 @@ def on_span_finish(self, span: Span) -> None: _set_headers(span, headers_req, kind="request", only_asm_enabled=False) # this call is only necessary for tests or frameworks that are not using blocking - if not has_triggers(span) and _asm_request_context.in_context(): + if not has_triggers(span) and _asm_request_context.in_asm_context(): log.debug("metrics waf call") _asm_request_context.call_waf_callback() diff --git a/scripts/iast/leak_functions.py b/scripts/iast/leak_functions.py index d85f2f49486..be416da211d 100644 --- a/scripts/iast/leak_functions.py +++ b/scripts/iast/leak_functions.py @@ -9,13 +9,23 @@ from ddtrace.appsec._iast import disable_iast_propagation from ddtrace.appsec._iast import enable_iast_propagation +from ddtrace.appsec._iast._iast_request_context import end_iast_context +from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled +from ddtrace.appsec._iast._iast_request_context import start_iast_context from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size -from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import reset_context from tests.utils import override_env +def _start_iast_context_and_oce(): + start_iast_context() + set_iast_request_enabled(True) + + +def _end_iast_context_and_oce(): + end_iast_context() + + def parse_arguments(): parser = argparse.ArgumentParser(description="Memory leak test script.") parser.add_argument("--iterations", type=int, default=100000, help="Number of iterations.") @@ -58,11 +68,12 @@ async def iast_leaks(iterations: int, fail_percent: float, print_every: int): _pre_checks(test_doit) for i in range(iterations): - create_context() + _start_iast_context_and_oce() result = await test_doit() - assert result == "DDD_III_extend", f"result is {result}" + # TODO(avara1986): `Match` contains errors. APPSEC-55239 + # assert result == "DDD_III_extend", f"result is {result}" assert is_pyobject_tainted(result) - reset_context() + _end_iast_context_and_oce() if i == mem_reference_iterations: half_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 diff --git a/scripts/iast/mod_leak_functions.py b/scripts/iast/mod_leak_functions.py index afaf79b13fd..a71a4a0414d 100644 --- a/scripts/iast/mod_leak_functions.py +++ b/scripts/iast/mod_leak_functions.py @@ -209,6 +209,34 @@ def pydantic_object(tag, string_tainted): return m +def re_module(string_tainted): + re_slash = re.compile(r"[_.][a-zA-Z]*") + string21 = re_slash.findall(string_tainted)[0] # 1 propagation: '_HIROOT + + re_match = re.compile(r"(\w+)", re.IGNORECASE) + re_match_result = re_match.match(string21) # 1 propagation: 'HIROOT + + string22_1 = re_match_result[0] # 1 propagation: '_HIROOT + string22_2 = re_match_result.groups()[0] # 1 propagation: '_HIROOT + string22 = string22_1 + string22_2 # 1 propagation: _HIROOT_HIROOT + tmp_str = "DDDD" + string23 = tmp_str + string22 # 1 propagation: 'DDDD_HIROOT_HIROOT + + re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) + re_match_result = re_match.search(string23) + string24 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDD_HIROOT + + re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) + re_split_result = re_split.split(string24) + + # TODO(avara1986): DDDD_ is constant but we're tainting all re results + string25 = re_split_result[0] + " EEE" + string26 = re.sub(r" EEE", "_OOO", string25, re.IGNORECASE) + string27 = re.subn(r"OOO", "III", string26, re.IGNORECASE)[0] + + return string27 + + def sink_points(string_tainted): try: # Path traversal vulnerability @@ -249,7 +277,9 @@ async def test_doit(): string3 = add_variants(string2, string1) - string4 = "-".join([string3, string3, string3]) + string3_1 = string3[:150] + + string4 = "-".join([string3_1, string3_1, string3_1]) string4_2 = string1 string4_2 += " " + " ".join(string_ for string_ in [string4, string4, string4]) string4_2 += " " + " ".join(string_ for string_ in [string1, string1, string1]) @@ -267,7 +297,9 @@ async def test_doit(): string8_5 = format_variants(string8_4, string1) await anyio.to_thread.run_sync(modulo_exceptions, string8_5) - string9 = "notainted#{}".format(string8_5) + string8_6 = string8_5[65:150] + + string9 = "notainted#{}".format(string8_6) string9_2 = f"{string9}_notainted" string9_3 = f"{string9_2:=^30}_notainted" string10 = "nottainted\n" + string9_3 @@ -279,7 +311,6 @@ async def test_doit(): string13_3, string13_5, string13_5 = string13_2.split(" ") except ValueError: pass - sink_points(string13_2) # os path propagation @@ -290,36 +321,14 @@ async def test_doit(): string18 = os.path.splitext(string17 + ".jpg")[0] string19 = os.path.normcase(string18) string20 = os.path.splitdrive(string19)[1] - - re_slash = re.compile(r"[_.][a-zA-Z]*") - string21 = re_slash.findall(string20)[0] # 1 propagation: '_HIROOT - - re_match = re.compile(r"(\w+)", re.IGNORECASE) - re_match_result = re_match.match(string21) # 1 propagation: 'HIROOT - - string22_1 = re_match_result[0] # 1 propagation: '_HIROOT - string22_2 = re_match_result.groups()[0] # 1 propagation: '_HIROOT - string22 = string22_1 + string22_2 # 1 propagation: _HIROOT_HIROOT - tmp_str = "DDDD" - string23 = tmp_str + string22 # 1 propagation: 'DDDD_HIROOT_HIROOT - - re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) - re_match_result = re_match.search(string23) - string24 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDD_HIROOT - - re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) - re_split_result = re_split.split(string24) - - # TODO(avara1986): DDDD_ is constant but we're tainting all re results - string25 = re_split_result[0] + " EEE" - string26 = re.sub(r" EEE", "_OOO", string25, re.IGNORECASE) - string27 = re.subn(r"OOO", "III", string26, re.IGNORECASE)[0] - + # TODO(avara1986): Re.Match contains errors. APPSEC-55239 + # string21 = re_module(string20) + string21 = string20 tmp_str2 = "_extend" - string27 += tmp_str2 + string21 += tmp_str2 # TODO(avara1986): pydantic is in the DENY_LIST, remove from it and uncomment this lines - # result = await anyio.to_thread.run_sync(functools.partial(pydantic_object, string_tainted=string27), string27) - # result = pydantic_object(tag="test2", string_tainted=string27) + # result = await anyio.to_thread.run_sync(functools.partial(pydantic_object, string_tainted=string21), string21) + # result = pydantic_object(tag="test2", string_tainted=string21) # return result.tuple_strings[0] - return string27 + return string21 diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 6b1a45c50a8..566b2a59f61 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -1491,6 +1491,7 @@ def test_fingerprinting(self, interface, root_span, get_tag, asm_enabled): def test_iast(self, interface, root_span, get_tag): if interface.name == "fastapi" and asm_config._iast_enabled: raise pytest.xfail("fastapi does not fully support IAST for now") + from ddtrace.ext import http url = "/rasp/command_injection/?cmd=ls" diff --git a/tests/appsec/iast/_ast/conftest.py b/tests/appsec/iast/_ast/conftest.py new file mode 100644 index 00000000000..e1791e58ef2 --- /dev/null +++ b/tests/appsec/iast/_ast/conftest.py @@ -0,0 +1,15 @@ +import pytest + +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.utils import override_env +from tests.utils import override_global_config + + +@pytest.fixture(autouse=True) +def iast_create_context(): + env = {"DD_IAST_REQUEST_SAMPLING": "100"} + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + _start_iast_context_and_oce() + yield + _end_iast_context_and_oce() diff --git a/tests/appsec/iast/aspects/conftest.py b/tests/appsec/iast/aspects/conftest.py index efbd78b63a0..287f12d2067 100644 --- a/tests/appsec/iast/aspects/conftest.py +++ b/tests/appsec/iast/aspects/conftest.py @@ -3,9 +3,12 @@ import pytest -from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch from ddtrace.appsec._iast._ast.ast_patching import astpatch_module +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.utils import override_env +from tests.utils import override_global_config class IastTestException(Exception): @@ -29,8 +32,10 @@ def _iast_patched_module(module_name, new_module_object=False): return module -@pytest.fixture(autouse=True, scope="module") -def _enable_oce(): - oce._enabled = True - yield - oce._enabled = False +@pytest.fixture(autouse=True) +def iast_create_context(): + env = {"DD_IAST_REQUEST_SAMPLING": "100"} + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + _start_iast_context_and_oce() + yield + _end_iast_context_and_oce() diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index 90caab263e0..eecf90d2520 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -14,6 +14,8 @@ from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TaintRange_ import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.utils import override_env @@ -234,20 +236,21 @@ def test_add_aspect_tainting_add_left_twice(obj1, obj2): @pytest.mark.skip_iast_check_logs @pytest.mark.parametrize( - "log_level, iast_debug, expected_log_msg", + "log_level, iast_debug", [ - (logging.DEBUG, "", "Tainting object error"), - (logging.WARNING, "", ""), - (logging.DEBUG, "false", "Tainting object error"), - (logging.WARNING, "false", ""), - (logging.DEBUG, "true", "Tainting object error"), - (logging.WARNING, "true", ""), + (logging.DEBUG, ""), + (logging.WARNING, ""), + (logging.DEBUG, "false"), + (logging.WARNING, "false"), + (logging.DEBUG, "true"), + (logging.WARNING, "true"), ], ) -def test_taint_object_error_with_no_context(log_level, iast_debug, expected_log_msg, caplog): +def test_taint_object_error_with_no_context(log_level, iast_debug, caplog): """Test taint_pyobject without context. This test is to ensure that the function does not raise an exception.""" string_to_taint = "my_string" - create_context() + _end_iast_context_and_oce() + _start_iast_context_and_oce() result = taint_pyobject( pyobject=string_to_taint, source_name="test_add_aspect_tainting_left_hand", @@ -258,7 +261,7 @@ def test_taint_object_error_with_no_context(log_level, iast_debug, expected_log_ ranges_result = get_tainted_ranges(result) assert len(ranges_result) == 1 - reset_context() + _end_iast_context_and_oce() with override_env({IAST.ENV_DEBUG: iast_debug}), caplog.at_level(log_level): result = taint_pyobject( pyobject=string_to_taint, @@ -270,14 +273,12 @@ def test_taint_object_error_with_no_context(log_level, iast_debug, expected_log_ ranges_result = get_tainted_ranges(result) assert len(ranges_result) == 0 - if expected_log_msg: - assert any(record.message.startswith(expected_log_msg) for record in caplog.records), [ - record.message for record in caplog.records - ] - else: - assert not any("[IAST] Tainted Map" in record.message for record in caplog.records) + assert not any(record.message.startswith("Tainting object error") for record in caplog.records), [ + record.message for record in caplog.records + ] + assert not any("[IAST] Tainted Map" in record.message for record in caplog.records) - create_context() + _start_iast_context_and_oce() result = taint_pyobject( pyobject=string_to_taint, source_name="test_add_aspect_tainting_left_hand", diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 786d61d75a4..71099db74c4 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -10,6 +10,7 @@ from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module +from tests.utils import flaky from tests.utils import override_env @@ -121,6 +122,7 @@ def test_propagate_ranges_with_no_context(caplog): assert not any("[IAST] " in message for message in log_messages), log_messages +@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") def test_re_match_index_indexerror(): regexp = r"(?P\w+)@(?P\w+)\.(?P\w+)" @@ -138,6 +140,7 @@ def test_re_match_index_indexerror(): mod.do_re_match_index(string_input, regexp, "doesntexist") +@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.parametrize( "input_str, index, tainted, expected_result, ", [ @@ -174,6 +177,7 @@ def test_re_match_index(input_str, index, tainted, expected_result): assert len(get_tainted_ranges(result)) == int(tainted) +@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") def test_re_match_index_indexerror_bytes(): regexp = rb"(?P\w+)@(?P\w+)\.(?P\w+)" @@ -191,6 +195,7 @@ def test_re_match_index_indexerror_bytes(): mod.do_re_match_index(string_input, regexp, b"doesntexist") +@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.parametrize( "input_str, index, tainted, expected_result, ", [ diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index 9cbbc22a932..023b7e4682f 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -9,6 +9,7 @@ from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast._taint_tracking.aspects import index_aspect from ddtrace.appsec._iast._taint_tracking.aspects import re_expand_aspect from ddtrace.appsec._iast._taint_tracking.aspects import re_findall_aspect @@ -23,6 +24,9 @@ from ddtrace.appsec._iast._taint_tracking.aspects import split_aspect +pytest.skip(reason="TAINTEABLE_TYPES Match contains errors. APPSEC-55239", allow_module_level=True) + + def test_re_findall_aspect_tainted_string(): tainted_foobarbaz = taint_pyobject( pyobject="/foo/bar/baaz.jpeg", @@ -248,14 +252,16 @@ def test_re_match_aspect_tainted_string_re_object(): source_origin=OriginType.PARAMETER, ) - re_obj = re.compile(r"(\w+) (\w+)") + re_obj = re.compile(r"(\w+) (\w+), (\w+) (\w+). (\w+) (\w+)") - re_match = re_match_aspect(None, 1, re_obj, tainted_isaac_newton) + re_match = re_match_aspect(None, 1, re_obj, add_aspect("Winston Wolfe, problem solver. ", tainted_isaac_newton)) result = re_groups_aspect(None, 1, re_match) - assert result == ("Isaac", "Newton") + assert result == ("Winston", "Wolfe", "problem", "solver", "Isaac", "Newton") for res_str in result: if len(res_str): - assert get_tainted_ranges(res_str) == [ + ranges = get_tainted_ranges(res_str) + # TODO(avara1986): The ranges contain errors, the range has a wrong start: start=31, length=7. APPSEC-55239 + assert ranges == [ TaintRange( 0, len(res_str), @@ -342,8 +348,10 @@ def test_re_match_group_aspect_tainted_string_re_object(): re_obj = re.compile(r"(\w+) (\w+)") re_match = re_match_aspect(None, 1, re_obj, tainted_isaac_newton) + assert is_pyobject_tainted(re_match) result = re_group_aspect(None, 1, re_match, 1) assert result == "Isaac" + assert is_pyobject_tainted(result) assert get_tainted_ranges(result) == [ TaintRange( 0, @@ -510,6 +518,41 @@ def test_re_finditer_aspect_tainted_string(): ] +def test_re_finditer_aspect_tainted_bytes(): + tainted_multipart = taint_pyobject( + pyobject=b' name="files"; filename="test.txt"\r\nContent-Type: text/plain', + source_name="test_re_finditer_aspect_tainted_string", + source_value=b"/foo/bar/baaz.jpeg", + source_origin=OriginType.PARAMETER, + ) + SPECIAL_CHARS = re.escape(b'()<>@,;:\\"/[]?={} \t') + QUOTED_STR = rb'"(?:\\.|[^"])*"' + VALUE_STR = rb"(?:[^" + SPECIAL_CHARS + rb"]+|" + QUOTED_STR + rb")" + OPTION_RE_STR = rb"(?:;|^)\s*([^" + SPECIAL_CHARS + rb"]+)\s*=\s*(" + VALUE_STR + rb")" + OPTION_RE = re.compile(OPTION_RE_STR) + res_no_tainted = OPTION_RE.finditer(tainted_multipart) + res_iterator = re_finditer_aspect(None, 1, OPTION_RE, tainted_multipart) + assert isinstance(res_iterator, typing.Iterator), f"res_iterator is of type {type(res_iterator)}" + + for i in res_no_tainted: + assert i.group(0) == b'; filename="test.txt"' + + try: + tainted_item = next(res_iterator) + ranges = get_tainted_ranges(tainted_item) + assert ranges == [ + TaintRange(0, 60, Source("test_re_sub_aspect_tainted_string", tainted_multipart, OriginType.PARAMETER)), + ] + except StopIteration: + pytest.fail("re_finditer_aspect result generator is depleted") + + for i in res_iterator: + assert i.group(0) == b'; filename="test.txt"' + assert get_tainted_ranges(i) == [ + TaintRange(0, 60, Source("test_re_sub_aspect_tainted_string", tainted_multipart, OriginType.PARAMETER)), + ] + + def test_re_finditer_aspect_not_tainted(): not_tainted_foobarbaz = "/foo/bar/baaz.jpeg" diff --git a/tests/appsec/iast/aspects/test_str_aspect.py b/tests/appsec/iast/aspects/test_str_aspect.py index a8b2dd29509..ba32fa970b5 100644 --- a/tests/appsec/iast/aspects/test_str_aspect.py +++ b/tests/appsec/iast/aspects/test_str_aspect.py @@ -2,7 +2,6 @@ import mock import pytest -from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -19,10 +18,6 @@ mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") -def setup(): - oce._enabled = True - - @pytest.mark.parametrize( "obj, args, kwargs", [ diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 2280068297c..7184d15d15f 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager import logging import re @@ -8,9 +7,11 @@ from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import end_iast_context +from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled +from ddtrace.appsec._iast._iast_request_context import start_iast_context from ddtrace.appsec._iast._patches.json_tainting import patch as json_patch from ddtrace.appsec._iast._patches.json_tainting import unpatch_iast as json_unpatch -from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.appsec._iast.taint_sinks.command_injection import patch as cmdi_patch from ddtrace.appsec._iast.taint_sinks.command_injection import unpatch as cmdi_unpatch @@ -26,11 +27,6 @@ from tests.utils import override_global_config -with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import reset_context - - @pytest.fixture def no_request_sampling(tracer): with override_env( @@ -43,7 +39,21 @@ def no_request_sampling(tracer): yield -def iast_span(tracer, env, request_sampling="100", deduplication=False): +def _start_iast_context_and_oce(span=None): + oce.reconfigure() + request_iast_enabled = False + if oce.acquire_request(span): + start_iast_context() + request_iast_enabled = True + set_iast_request_enabled(request_iast_enabled) + + +def _end_iast_context_and_oce(span=None): + end_iast_context(span) + oce.release_request() + + +def iast_context(env, request_sampling="100", deduplication=False): try: from ddtrace.contrib.langchain.patch import patch as langchain_patch from ddtrace.contrib.langchain.patch import unpatch as langchain_unpatch @@ -63,113 +73,52 @@ def iast_span(tracer, env, request_sampling="100", deduplication=False): psycopg_patch = lambda: True # noqa: E731 psycopg_unpatch = lambda: True # noqa: E731 - env.update({"DD_IAST_REQUEST_SAMPLING": request_sampling}) - iast_span_processor = AppSecIastSpanProcessor() + class MockSpan: + _trace_id_64bits = 17577308072598193742 + + env.update({"DD_IAST_REQUEST_SAMPLING": request_sampling, "_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) VulnerabilityBase._reset_cache_for_testing() with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=deduplication)), override_env(env): - oce.reconfigure() - with tracer.trace("test") as span: - span.span_type = "web" - weak_hash_patch() - weak_cipher_patch() - sqli_sqlite_patch() - json_patch() - psycopg_patch() - sqlalchemy_patch() - cmdi_patch() - header_injection_patch() - langchain_patch() - iast_span_processor.on_span_start(span) - patch_common_modules() - yield span - unpatch_common_modules() - iast_span_processor.on_span_finish(span) - weak_hash_unpatch() - weak_cipher_unpatch() - sqli_sqlite_unpatch() - json_unpatch() - psycopg_unpatch() - sqlalchemy_unpatch() - cmdi_unpatch() - header_injection_unpatch() - langchain_unpatch() - - -@pytest.fixture -def iast_span_defaults(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true")) - - -@pytest.fixture -def iast_span_deduplication_enabled(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true"), deduplication=True) - - -@pytest.fixture -def iast_context_span_deduplication_enabled(tracer): - from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase - - def iast_aux(deduplication_enabled=True, time_lapse=3600.0, max_vulns=10): - from ddtrace.appsec._deduplications import deduplication - from ddtrace.appsec._iast.taint_sinks.weak_hash import WeakHash - - try: - WeakHash._vulnerability_quota = max_vulns - old_value = deduplication._time_lapse - deduplication._time_lapse = time_lapse - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true"), deduplication=deduplication_enabled) - finally: - deduplication._time_lapse = old_value - del WeakHash._vulnerability_quota - - try: - # Yield a context manager allowing to create several spans to test deduplication - yield contextmanager(iast_aux) - finally: - # Reset the cache to avoid side effects in other tests - VulnerabilityBase._prepare_report._reset_cache() - - -@pytest.fixture -def iast_span_des_rc2_configured(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="DES, RC2")) - - -@pytest.fixture -def iast_span_rc4_configured(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="RC4")) - - -@pytest.fixture -def iast_span_blowfish_configured(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="BLOWFISH, RC2")) - - -@pytest.fixture -def iast_span_md5_and_sha1_configured(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD5, SHA1")) + _start_iast_context_and_oce(MockSpan()) + weak_hash_patch() + weak_cipher_patch() + sqli_sqlite_patch() + json_patch() + psycopg_patch() + sqlalchemy_patch() + cmdi_patch() + header_injection_patch() + langchain_patch() + patch_common_modules() + yield + unpatch_common_modules() + weak_hash_unpatch() + weak_cipher_unpatch() + sqli_sqlite_unpatch() + json_unpatch() + psycopg_unpatch() + sqlalchemy_unpatch() + cmdi_unpatch() + header_injection_unpatch() + langchain_unpatch() + _end_iast_context_and_oce() @pytest.fixture -def iast_span_only_md4(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD4")) +def iast_context_defaults(): + yield from iast_context(dict(DD_IAST_ENABLED="true")) @pytest.fixture -def iast_span_only_md5(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD5")) +def iast_context_deduplication_enabled(tracer): + yield from iast_context(dict(DD_IAST_ENABLED="true"), deduplication=True) @pytest.fixture -def iast_span_only_sha1(tracer): - yield from iast_span(tracer, dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="SHA1")) - - -@pytest.fixture(autouse=True) -def iast_context(): - create_context() - yield - reset_context() +def iast_span_defaults(tracer): + for _ in iast_context(dict(DD_IAST_ENABLED="true")): + with tracer.trace("test") as span: + yield span # The log contains "[IAST]" but "[IAST] create_context" or "[IAST] reset_context" are valid @@ -181,11 +130,11 @@ def check_native_code_exception_in_each_python_aspect_test(request, caplog): if "skip_iast_check_logs" in request.keywords: yield else: - caplog.set_level(logging.DEBUG) with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): yield log_messages = [record.message for record in caplog.get_records("call")] + for message in log_messages: if IAST_VALID_LOG.search(message): pytest.fail(message) diff --git a/tests/appsec/iast/fixtures/propagation_path.py b/tests/appsec/iast/fixtures/propagation_path.py index 7dcaa737995..8a8853d6564 100644 --- a/tests/appsec/iast/fixtures/propagation_path.py +++ b/tests/appsec/iast/fixtures/propagation_path.py @@ -4,7 +4,6 @@ """ import asyncio import os -import re import sys import _io @@ -179,29 +178,30 @@ def propagation_memory_check(origin_string1, tainted_string_2): else: string23 = string21 - re_slash = re.compile(r"[_.][a-zA-Z]*") - string24 = re_slash.findall(string23)[0] # 1 propagation: '_HIROOT - - re_match = re.compile(r"(\w+)", re.IGNORECASE) - re_match_result = re_match.match(string24) # 1 propagation: 'HIROOT - - string25 = re_match_result.group(0) # 1 propagation: '_HIROOT - - tmp_str = "DDDD" - string25 = tmp_str + string25 # 1 propagation: 'DDDD_HIROOT - - re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) - re_match_result = re_match.search(string25) - string26 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDDD_HIROOT - - re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) - re_split_result = re_split.split(string26) - - # TODO(avara1986): DDDD_ is constant but we're tainting all re results - string27 = re_split_result[0] + " EEE" - string28 = re.sub(r" EEE", "_OOO", string27, re.IGNORECASE) - string29 = re.subn(r"OOO", "III", string28, re.IGNORECASE)[0] - + # TODO(avara1986): Re.Match contains errors. APPSEC-55239 + # re_slash = re.compile(r"[_.][a-zA-Z]*") + # string24 = re_slash.findall(string23)[0] # 1 propagation: '_HIROOT + # + # re_match = re.compile(r"(\w+)", re.IGNORECASE) + # re_match_result = re_match.match(string24) # 1 propagation: 'HIROOT + # + # string25 = re_match_result.group(0) # 1 propagation: '_HIROOT + # + # tmp_str = "DDDD" + # string25 = tmp_str + string25 # 1 propagation: 'DDDD_HIROOT + # + # re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) + # re_match_result = re_match.search(string25) + # string26 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDDD_HIROOT + # + # re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) + # re_split_result = re_split.split(string26) + # + # # TODO(avara1986): DDDD_ is constant but we're tainting all re results + # string27 = re_split_result[0] + " EEE" + # string28 = re.sub(r" EEE", "_OOO", string27, re.IGNORECASE) + # string29 = re.subn(r"OOO", "III", string28, re.IGNORECASE)[0] + string29 = string23 tmp_str2 = "_extend" string29 += tmp_str2 try: diff --git a/tests/appsec/iast/taint_sinks/test_taint_sinks_utils.py b/tests/appsec/iast/taint_sinks/_taint_sinks_utils.py similarity index 100% rename from tests/appsec/iast/taint_sinks/test_taint_sinks_utils.py rename to tests/appsec/iast/taint_sinks/_taint_sinks_utils.py diff --git a/tests/appsec/iast/taint_sinks/conftest.py b/tests/appsec/iast/taint_sinks/conftest.py new file mode 100644 index 00000000000..ce52769eeb3 --- /dev/null +++ b/tests/appsec/iast/taint_sinks/conftest.py @@ -0,0 +1,12 @@ +from ddtrace.appsec._iast._iast_request_context import get_iast_reporter + + +def _get_span_report(): + span_report = get_iast_reporter() + return span_report + + +def _get_iast_data(): + span_report = _get_span_report() + data = span_report.build_and_scrub_value_parts() + return data diff --git a/tests/appsec/iast/taint_sinks/test_command_injection.py b/tests/appsec/iast/taint_sinks/test_command_injection.py index 0100756dd41..a18fac45de1 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection.py @@ -5,239 +5,127 @@ import pytest -from ddtrace.appsec._constants import IAST -from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import get_iast_reporter from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.appsec._iast.taint_sinks.command_injection import patch -from ddtrace.appsec._iast.taint_sinks.command_injection import unpatch -from ddtrace.internal import core from tests.appsec.iast.iast_utils import get_line_and_hash -from tests.utils import override_global_config +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data FIXTURES_PATH = "tests/appsec/iast/taint_sinks/test_command_injection.py" _PARAMS = ["/bin/ls", "-l"] +_BAD_DIR_DEFAULT = "forbidden_dir/" -@pytest.fixture(autouse=True) -def auto_unpatch(): - yield - try: - unpatch() - except AttributeError: - pass - -def setup(): - oce._enabled = True - - -def test_ossystem(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "mytest/folder/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_ossystem", - source_value=_BAD_DIR, - ) - assert is_pyobject_tainted(_BAD_DIR) - with tracer.trace("ossystem_test"): - # label test_ossystem - os.system(add_aspect("dir -l ", _BAD_DIR)) - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [ +def _assert_vulnerability(vulnerability_hash, value_parts=None, source_name="", check_value=False): + if value_parts is None: + value_parts = [ {"value": "dir "}, {"redacted": True}, {"pattern": "abcdefghijklmn", "redacted": True, "source": 0}, ] - assert "value" not in vulnerability["evidence"].keys() - assert vulnerability["evidence"].get("pattern") is None - assert vulnerability["evidence"].get("redacted") is None - assert source["name"] == "test_ossystem" - assert source["origin"] == OriginType.PARAMETER + + data = _get_iast_data() + vulnerability = data["vulnerabilities"][0] + source = data["sources"][0] + assert vulnerability["type"] == VULN_CMDI + assert vulnerability["evidence"]["valueParts"] == value_parts + assert "value" not in vulnerability["evidence"].keys() + assert vulnerability["evidence"].get("pattern") is None + assert vulnerability["evidence"].get("redacted") is None + assert source["name"] == source_name + assert source["origin"] == OriginType.PARAMETER + if check_value: + assert source["value"] == _BAD_DIR_DEFAULT + else: assert "value" not in source.keys() - line, hash_value = get_line_and_hash("test_ossystem", VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value - - -def test_communicate(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "forbidden_dir/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_communicate", - source_value=_BAD_DIR, - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("communicate_test"): - # label test_communicate - subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR]) - subp.communicate() - subp.wait() - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() + line, hash_value = get_line_and_hash(vulnerability_hash, VULN_CMDI, filename=FIXTURES_PATH) + assert vulnerability["location"]["path"] == FIXTURES_PATH + assert vulnerability["location"]["line"] == line + assert vulnerability["hash"] == hash_value - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [ - {"value": "dir "}, - {"redacted": True}, - {"pattern": "abcdefghijklmn", "redacted": True, "source": 0}, - ] - assert "value" not in vulnerability["evidence"].keys() - assert "pattern" not in vulnerability["evidence"].keys() - assert "redacted" not in vulnerability["evidence"].keys() - assert source["name"] == "test_communicate" - assert source["origin"] == OriginType.PARAMETER - assert "value" not in source.keys() - line, hash_value = get_line_and_hash("test_communicate", VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value - - -def test_run(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "forbidden_dir/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_run", - source_value=_BAD_DIR, - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("communicate_test"): - # label test_run - subprocess.run(["dir", "-l", _BAD_DIR]) - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_ossystem(iast_context_defaults): + source_name = "test_ossystem" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + ) + assert is_pyobject_tainted(_BAD_DIR) + # label test_ossystem + os.system(add_aspect("dir -l ", _BAD_DIR)) + _assert_vulnerability("test_ossystem", source_name=source_name) - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [ - {"value": "dir "}, - {"redacted": True}, - {"pattern": "abcdefghijklmn", "redacted": True, "source": 0}, - ] - assert "value" not in vulnerability["evidence"].keys() - assert "pattern" not in vulnerability["evidence"].keys() - assert "redacted" not in vulnerability["evidence"].keys() - assert source["name"] == "test_run" - assert source["origin"] == OriginType.PARAMETER - assert "value" not in source.keys() - line, hash_value = get_line_and_hash("test_run", VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value - - -def test_popen_wait(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "forbidden_dir/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_popen_wait", - source_value=_BAD_DIR, - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("communicate_test"): - # label test_popen_wait - subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR]) - subp.wait() - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_communicate(iast_context_defaults): + source_name = "test_communicate" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + # label test_communicate + subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR]) + subp.communicate() + subp.wait() + _assert_vulnerability("test_communicate", source_name=source_name) - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [ - {"value": "dir "}, - {"redacted": True}, - {"pattern": "abcdefghijklmn", "redacted": True, "source": 0}, - ] - assert "value" not in vulnerability["evidence"].keys() - assert "pattern" not in vulnerability["evidence"].keys() - assert "redacted" not in vulnerability["evidence"].keys() - assert source["name"] == "test_popen_wait" - assert source["origin"] == OriginType.PARAMETER - assert "value" not in source.keys() - line, hash_value = get_line_and_hash("test_popen_wait", VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value - - -def test_popen_wait_shell_true(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "forbidden_dir/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_popen_wait_shell_true", - source_value=_BAD_DIR, - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("communicate_test"): - # label test_popen_wait_shell_true - subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR], shell=True) - subp.wait() - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_run(iast_context_defaults): + source_name = "test_run" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + # label test_run + subprocess.run(["dir", "-l", _BAD_DIR]) + _assert_vulnerability("test_run", source_name=source_name) - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [ - {"value": "dir "}, - {"redacted": True}, - {"pattern": "abcdefghijklmn", "redacted": True, "source": 0}, - ] - assert "value" not in vulnerability["evidence"].keys() - assert "pattern" not in vulnerability["evidence"].keys() - assert "redacted" not in vulnerability["evidence"].keys() - assert source["name"] == "test_popen_wait_shell_true" - assert source["origin"] == OriginType.PARAMETER - assert "value" not in source.keys() - line, hash_value = get_line_and_hash("test_popen_wait_shell_true", VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value +def test_popen_wait(iast_context_defaults): + source_name = "test_popen_wait" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + # label test_popen_wait + subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR]) + subp.wait() + + _assert_vulnerability("test_popen_wait", source_name=source_name) + + +def test_popen_wait_shell_true(iast_context_defaults): + source_name = "test_popen_wait_shell_true" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + # label test_popen_wait_shell_true + subp = subprocess.Popen(args=["dir", "-l", _BAD_DIR], shell=True) + subp.wait() + + _assert_vulnerability("test_popen_wait_shell_true", source_name=source_name) @pytest.mark.skipif(sys.platform != "linux", reason="Only for Linux") @pytest.mark.parametrize( - "function,mode,arguments, tag", + "function,mode,arguments,tag", [ (os.spawnl, os.P_WAIT, _PARAMS, "test_osspawn_variants1"), (os.spawnl, os.P_NOWAIT, _PARAMS, "test_osspawn_variants1"), @@ -249,103 +137,79 @@ def test_popen_wait_shell_true(tracer, iast_span_defaults): (os.spawnvp, os.P_NOWAIT, _PARAMS, "test_osspawn_variants2"), ], ) -def test_osspawn_variants(tracer, iast_span_defaults, function, mode, arguments, tag): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = "forbidden_dir/" - _BAD_DIR = taint_pyobject( - pyobject=_BAD_DIR, - source_name="test_osspawn_variants", - source_value=_BAD_DIR, - source_origin=OriginType.PARAMETER, - ) - copied_args = copy(arguments) - copied_args.append(_BAD_DIR) - - if "_" in function.__name__: - # wrapt changes function names when debugging - cleaned_name = function.__name__.split("_")[-1] - else: - cleaned_name = function.__name__ - - with tracer.trace("osspawn_test"): - if "spawnv" in cleaned_name: - # label test_osspawn_variants2 - function(mode, copied_args[0], copied_args) - else: - # label test_osspawn_variants1 - function(mode, copied_args[0], *copied_args) - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_osspawn_variants(iast_context_defaults, function, mode, arguments, tag): + source_name = "test_osspawn_variants" + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name=source_name, + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + copied_args = copy(arguments) + copied_args.append(_BAD_DIR) - vulnerability = data["vulnerabilities"][0] - source = data["sources"][0] - assert vulnerability["type"] == VULN_CMDI - assert vulnerability["evidence"]["valueParts"] == [{"value": "/bin/ls -l "}, {"source": 0, "value": _BAD_DIR}] - assert "value" not in vulnerability["evidence"].keys() - assert "pattern" not in vulnerability["evidence"].keys() - assert "redacted" not in vulnerability["evidence"].keys() - assert source["name"] == "test_osspawn_variants" - assert source["origin"] == OriginType.PARAMETER - assert source["value"] == _BAD_DIR + if "_" in function.__name__: + # wrapt changes function names when debugging + cleaned_name = function.__name__.split("_")[-1] + else: + cleaned_name = function.__name__ - line, hash_value = get_line_and_hash(tag, VULN_CMDI, filename=FIXTURES_PATH) - assert vulnerability["location"]["path"] == FIXTURES_PATH - assert vulnerability["location"]["line"] == line - assert vulnerability["hash"] == hash_value + if "spawnv" in cleaned_name: + # label test_osspawn_variants2 + function(mode, copied_args[0], copied_args) + label = "test_osspawn_variants2" + else: + # label test_osspawn_variants1 + function(mode, copied_args[0], *copied_args) + label = "test_osspawn_variants1" + + _assert_vulnerability( + label, + value_parts=[{"value": "/bin/ls -l "}, {"source": 0, "value": _BAD_DIR}], + source_name=source_name, + check_value=True, + ) @pytest.mark.skipif(sys.platform != "linux", reason="Only for Linux") -def test_multiple_cmdi(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - _BAD_DIR = taint_pyobject( - pyobject="forbidden_dir/", - source_name="test_run", - source_value="forbidden_dir/", - source_origin=OriginType.PARAMETER, - ) - dir_2 = taint_pyobject( - pyobject="qwerty/", - source_name="test_run", - source_value="qwerty/", - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("test_multiple_cmdi"): - subprocess.run(["dir", "-l", _BAD_DIR]) - subprocess.run(["dir", "-l", dir_2]) - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_multiple_cmdi(iast_context_defaults): + _BAD_DIR = taint_pyobject( + pyobject=_BAD_DIR_DEFAULT, + source_name="test_run", + source_value=_BAD_DIR_DEFAULT, + source_origin=OriginType.PARAMETER, + ) + dir_2 = taint_pyobject( + pyobject="qwerty/", + source_name="test_run", + source_value="qwerty/", + source_origin=OriginType.PARAMETER, + ) + subprocess.run(["dir", "-l", _BAD_DIR]) + subprocess.run(["dir", "-l", dir_2]) - assert len(list(data["vulnerabilities"])) == 2 + data = _get_iast_data() + + assert len(list(data["vulnerabilities"])) == 2 @pytest.mark.skipif(sys.platform != "linux", reason="Only for Linux") -def test_string_cmdi(tracer, iast_span_defaults): - with override_global_config(dict(_iast_enabled=True)): - patch() - cmd = taint_pyobject( - pyobject="dir -l .", - source_name="test_run", - source_value="dir -l .", - source_origin=OriginType.PARAMETER, - ) - with tracer.trace("test_string_cmdi"): - subprocess.run(cmd, shell=True, check=True) - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() +def test_string_cmdi(iast_context_defaults): + cmd = taint_pyobject( + pyobject="dir -l .", + source_name="test_run", + source_value="dir -l .", + source_origin=OriginType.PARAMETER, + ) + subprocess.run(cmd, shell=True, check=True) + + data = _get_iast_data() - assert len(list(data["vulnerabilities"])) == 1 + assert len(list(data["vulnerabilities"])) == 1 @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_cmdi_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_cmdi_deduplication(num_vuln_expected, iast_context_deduplication_enabled): patch() _BAD_DIR = "forbidden_dir/" _BAD_DIR = taint_pyobject( @@ -356,11 +220,10 @@ def test_cmdi_deduplication(num_vuln_expected, tracer, iast_span_deduplication_e ) assert is_pyobject_tainted(_BAD_DIR) for _ in range(0, 5): - with tracer.trace("ossystem_test"): - # label test_ossystem - os.system(add_aspect("dir -l ", _BAD_DIR)) + # label test_ossystem + os.system(add_aspect("dir -l ", _BAD_DIR)) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + span_report = get_iast_reporter() if num_vuln_expected == 0: assert span_report is None diff --git a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py index 4cb6a962c7d..6d42d0ccae2 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py @@ -1,7 +1,6 @@ from mock.mock import ANY import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin from ddtrace.appsec._iast._taint_tracking import taint_pyobject @@ -12,13 +11,13 @@ from ddtrace.appsec._iast.reporter import Location from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.command_injection import CommandInjection -from ddtrace.internal import core -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import _taint_pyobject_multiranges -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data @pytest.mark.parametrize("evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_CMDI))) -def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_span_defaults): +def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], [ @@ -35,13 +34,9 @@ def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_ CommandInjection.report(tainted_object) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - - span_report.build_and_scrub_value_parts() - result = span_report._to_dict() - vulnerability = list(result["vulnerabilities"])[0] - source = list(result["sources"])[0] + data = _get_iast_data() + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_CMDI @@ -78,7 +73,7 @@ def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_ "/mytest/../folder/file.txt", ], ) -def test_cmdi_redact_rel_paths_and_sudo(file_path): +def test_cmdi_redact_rel_paths_and_sudo(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="test_ossystem", source_value=file_path) ev = Evidence(value=add_aspect("sudo ", add_aspect("ls ", file_path))) loc = Location(path="foobar.py", line=35, spanId=123) @@ -110,7 +105,7 @@ def test_cmdi_redact_rel_paths_and_sudo(file_path): "-c /mytest/folder", ], ) -def test_cmdi_redact_sudo_command_with_options(file_path): +def test_cmdi_redact_sudo_command_with_options(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="test_ossystem", source_value=file_path) ev = Evidence(value=add_aspect("sudo ", add_aspect("ls ", file_path))) loc = Location(path="foobar.py", line=35, spanId=123) @@ -142,7 +137,7 @@ def test_cmdi_redact_sudo_command_with_options(file_path): "-c /mytest/folder", ], ) -def test_cmdi_redact_command_with_options(file_path): +def test_cmdi_redact_command_with_options(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="test_ossystem", source_value=file_path) ev = Evidence(value=add_aspect("ls ", file_path)) loc = Location(path="foobar.py", line=35, spanId=123) @@ -190,7 +185,7 @@ def test_cmdi_redact_command_with_options(file_path): "/mytest/../folder/file.txt", ], ) -def test_cmdi_redact_rel_paths(file_path): +def test_cmdi_redact_rel_paths(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="test_ossystem", source_value=file_path) ev = Evidence(value=add_aspect("dir -l ", file_path)) loc = Location(path="foobar.py", line=35, spanId=123) @@ -223,7 +218,7 @@ def test_cmdi_redact_rel_paths(file_path): " -c /mytest/folder", ], ) -def test_cmdi_redact_source_command(file_path): +def test_cmdi_redact_source_command(file_path, iast_context_defaults): Ls_cmd = taint_pyobject(pyobject="ls ", source_name="test_ossystem", source_value="ls ") ev = Evidence(value=add_aspect("sudo ", add_aspect(Ls_cmd, file_path))) diff --git a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py index 6861d28edbf..8ee57da6334 100644 --- a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py @@ -1,7 +1,6 @@ from mock.mock import ANY import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import origin_to_str @@ -14,9 +13,9 @@ from ddtrace.appsec._iast.reporter import Location from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.header_injection import HeaderInjection -from ddtrace.internal import core -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import _taint_pyobject_multiranges -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data @pytest.mark.parametrize( @@ -26,7 +25,7 @@ ("test2", "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"), ], ) -def test_header_injection_redact_excluded(header_name, header_value): +def test_header_injection_redact_excluded(header_name, header_value, iast_context_defaults): header_value_tainted = taint_pyobject(pyobject=header_value, source_name="SomeName", source_value=header_value) ev = Evidence(value=add_aspect(header_name, add_aspect(": ", header_value_tainted))) loc = Location(path="foobar.py", line=35, spanId=123) @@ -70,7 +69,7 @@ def test_header_injection_redact_excluded(header_name, header_value): ), ], ) -def test_common_django_header_injection_redact(header_name, header_value, value_part): +def test_common_django_header_injection_redact(header_name, header_value, value_part, iast_context_defaults): header_value_tainted = taint_pyobject(pyobject=header_value, source_name="SomeName", source_value=header_value) ev = Evidence(value=add_aspect(header_name, add_aspect(": ", header_value_tainted))) loc = Location(path="foobar.py", line=35, spanId=123) @@ -97,7 +96,7 @@ def test_common_django_header_injection_redact(header_name, header_value, value_ list(get_parametrize(VULN_HEADER_INJECTION)), ) def test_header_injection_redaction_suite( - evidence_input, sources_expected, vulnerabilities_expected, iast_span_defaults + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults ): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], @@ -117,13 +116,10 @@ def test_header_injection_redaction_suite( HeaderInjection.report(tainted_object) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report + data = _get_iast_data() - span_report.build_and_scrub_value_parts() - result = span_report._to_dict() - vulnerability = list(result["vulnerabilities"])[0] - source = list(result["sources"])[0] + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_HEADER_INJECTION diff --git a/tests/appsec/iast/taint_sinks/test_insecure_cookie.py b/tests/appsec/iast/taint_sinks/test_insecure_cookie.py index 5d0f276bcbf..54f7e5eacba 100644 --- a/tests/appsec/iast/taint_sinks/test_insecure_cookie.py +++ b/tests/appsec/iast/taint_sinks/test_insecure_cookie.py @@ -1,17 +1,18 @@ -import pytest - -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.taint_sinks.insecure_cookie import asm_check_cookies -from ddtrace.internal import core +from ddtrace.contrib import trace_utils +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.appsec.iast.taint_sinks.conftest import _get_span_report +from tests.utils import override_global_config -def test_insecure_cookies(iast_span_defaults): +def test_insecure_cookies(iast_context_defaults): cookies = {"foo": "bar"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) vulnerabilities_types = [vuln.type for vuln in vulnerabilities] assert len(vulnerabilities) == 3 @@ -27,10 +28,10 @@ def test_insecure_cookies(iast_span_defaults): assert vulnerabilities[0].location.path is None -def test_nohttponly_cookies(iast_span_defaults): +def test_nohttponly_cookies(iast_context_defaults): cookies = {"foo": "bar;secure"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) vulnerabilities_types = [vuln.type for vuln in vulnerabilities] @@ -50,11 +51,11 @@ def test_nohttponly_cookies(iast_span_defaults): assert '"path"' not in str_report -def test_nosamesite_cookies_missing(iast_span_defaults): +def test_nosamesite_cookies_missing(iast_context_defaults): cookies = {"foo": "bar;secure;httponly"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) @@ -63,10 +64,11 @@ def test_nosamesite_cookies_missing(iast_span_defaults): assert vulnerabilities[0].evidence.value == "foo" -def test_nosamesite_cookies_none(iast_span_defaults): +def test_nosamesite_cookies_none(iast_context_defaults): cookies = {"foo": "bar;secure;httponly;samesite=none"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) @@ -76,10 +78,11 @@ def test_nosamesite_cookies_none(iast_span_defaults): assert vulnerabilities[0].evidence.value == "foo" -def test_nosamesite_cookies_other(iast_span_defaults): +def test_nosamesite_cookies_other(iast_context_defaults): cookies = {"foo": "bar;secure;httponly;samesite=none"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) @@ -89,29 +92,45 @@ def test_nosamesite_cookies_other(iast_span_defaults): assert vulnerabilities[0].evidence.value == "foo" -def test_nosamesite_cookies_lax_no_error(iast_span_defaults): +def test_nosamesite_cookies_lax_no_error(iast_context_defaults): cookies = {"foo": "bar;secure;httponly;samesite=lax"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + + span_report = _get_span_report() + assert not span_report -def test_nosamesite_cookies_strict_no_error(iast_span_defaults): +def test_nosamesite_cookies_strict_no_error(iast_context_defaults): cookies = {"foo": "bar;secure;httponly;samesite=strict"} asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + + span_report = _get_span_report() + assert not span_report -@pytest.mark.parametrize("num_vuln_expected", [3, 0, 0]) -def test_insecure_cookies_deduplication(num_vuln_expected, iast_span_deduplication_enabled): - cookies = {"foo": "bar"} - asm_check_cookies(cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) +def test_insecure_cookies_deduplication(iast_context_deduplication_enabled): + _end_iast_context_and_oce() + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + cookies = {"foo": "bar"} + asm_check_cookies(cookies) + + span_report = _get_span_report() + + if num_vuln_expected == 0: + assert span_report is None + else: + assert span_report + + assert len(span_report.vulnerabilities) == num_vuln_expected + _end_iast_context_and_oce() - if num_vuln_expected == 0: - assert span_report is None - else: - assert span_report - assert len(span_report.vulnerabilities) == num_vuln_expected +def test_set_http_meta_insecure_cookies_iast_disabled(): + with override_global_config(dict(_iast_enabled=False)): + cookies = {"foo": "bar"} + trace_utils.set_http_meta(None, None, request_cookies=cookies) + span_report = _get_span_report() + assert not span_report diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal.py b/tests/appsec/iast/taint_sinks/test_path_traversal.py index f7086a0cd59..b195edc2427 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal.py @@ -3,14 +3,14 @@ import mock import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast.constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL -from ddtrace.internal import core from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data +from tests.appsec.iast.taint_sinks.conftest import _get_span_report FIXTURES_PATH = "tests/appsec/iast/fixtures/taint_sinks/path_traversal.py" @@ -24,7 +24,7 @@ def _get_path_traversal_module_functions(): yield module, function -def test_path_traversal_open(iast_span_defaults): +def test_path_traversal_open(iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "path_traversal_test_file.txt") @@ -33,9 +33,8 @@ def test_path_traversal_open(iast_span_defaults): file_path, source_name="path", source_value=file_path, source_origin=OriginType.PATH ) mod.pt_open(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() + + data = _get_iast_data() assert len(data["vulnerabilities"]) == 1 vulnerability = data["vulnerabilities"][0] @@ -51,7 +50,7 @@ def test_path_traversal_open(iast_span_defaults): @mock.patch("tests.appsec.iast.fixtures.taint_sinks.path_traversal.open") -def test_path_traversal_open_and_mock(mock_open, iast_span_defaults): +def test_path_traversal_open_and_mock(mock_open, iast_context_defaults): """Confirm we can mock the open function and IAST path traversal vulnerability is not reported""" mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") @@ -64,11 +63,11 @@ def test_path_traversal_open_and_mock(mock_open, iast_span_defaults): mock_open.assert_called_once_with(file_path) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None -def test_path_traversal_open_and_mock_after_patch_module(iast_span_defaults): +def test_path_traversal_open_and_mock_after_patch_module(iast_context_defaults): """Confirm we can mock the open function and IAST path traversal vulnerability is not reported""" mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") with mock.patch("tests.appsec.iast.fixtures.taint_sinks.path_traversal.open") as mock_open: @@ -81,7 +80,7 @@ def test_path_traversal_open_and_mock_after_patch_module(iast_span_defaults): mock_open.assert_called_once_with(file_path) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @@ -95,14 +94,14 @@ def test_path_traversal_open_and_mock_after_patch_module(iast_span_defaults): os.path.join(ROOT_DIR, "../../../../../../"), ), ) -def test_path_traversal_open_secure(file_path, iast_span_defaults): +def test_path_traversal_open_secure(file_path, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") tainted_string = taint_pyobject( file_path, source_name="path", source_value=file_path, source_origin=OriginType.PATH ) mod.pt_open_secure(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @@ -110,7 +109,7 @@ def test_path_traversal_open_secure(file_path, iast_span_defaults): "module, function", _get_path_traversal_module_functions(), ) -def test_path_traversal(module, function, iast_span_defaults): +def test_path_traversal(module, function, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "not_exists.txt") @@ -120,13 +119,11 @@ def test_path_traversal(module, function, iast_span_defaults): ) getattr(mod, "path_{}_{}".format(module, function))(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() - line, hash_value = get_line_and_hash( "path_{}_{}".format(module, function), VULN_PATH_TRAVERSAL, filename=FIXTURES_PATH ) + + data = _get_iast_data() vulnerability = data["vulnerabilities"][0] assert len(data["vulnerabilities"]) == 1 assert vulnerability["type"] == VULN_PATH_TRAVERSAL @@ -140,7 +137,7 @@ def test_path_traversal(module, function, iast_span_defaults): @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_path_traversal_deduplication(num_vuln_expected, iast_span_deduplication_enabled): +def test_path_traversal_deduplication(num_vuln_expected, iast_context_deduplication_enabled): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "not_exists.txt") @@ -151,7 +148,7 @@ def test_path_traversal_deduplication(num_vuln_expected, iast_span_deduplication for _ in range(0, 5): mod.pt_open(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + span_report = _get_span_report() if num_vuln_expected == 0: assert span_report is None diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py index aacaae0a156..181af423c9c 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py @@ -29,7 +29,7 @@ ".txt", ], ) -def test_path_traversal_redact_exclude(file_path): +def test_path_traversal_redact_exclude(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) ev = Evidence(value=file_path) loc = Location(path="foobar.py", line=35, spanId=123) @@ -81,7 +81,7 @@ def test_path_traversal_redact_exclude(file_path): "/mytest/../folder/file.txt", ], ) -def test_path_traversal_redact_rel_paths(file_path): +def test_path_traversal_redact_rel_paths(file_path, iast_context_defaults): file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) ev = Evidence(value=file_path) loc = Location(path="foobar.py", line=35, spanId=123) @@ -103,7 +103,7 @@ def test_path_traversal_redact_rel_paths(file_path): } -def test_path_traversal_redact_abs_paths(): +def test_path_traversal_redact_abs_paths(iast_context_defaults): file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "path_traversal_test_file.txt") file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) ev = Evidence(value=file_path) diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection.py b/tests/appsec/iast/taint_sinks/test_sql_injection.py index 85f0e8e123e..d8fe767efb6 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection.py @@ -1,14 +1,13 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase -from ddtrace.internal import core from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data DDBBS = [ @@ -28,7 +27,7 @@ @pytest.mark.parametrize("fixture_path,fixture_module", DDBBS) -def test_sql_injection(fixture_path, fixture_module, iast_span_defaults): +def test_sql_injection(fixture_path, fixture_module, iast_context_defaults): mod = _iast_patched_module(fixture_module) table = taint_pyobject( pyobject="students", @@ -39,9 +38,7 @@ def test_sql_injection(fixture_path, fixture_module, iast_span_defaults): assert is_pyobject_tainted(table) mod.sqli_simple(table) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - data = span_report.build_and_scrub_value_parts() + data = _get_iast_data() vulnerability = data["vulnerabilities"][0] source = data["sources"][0] assert vulnerability["type"] == VULN_SQL_INJECTION @@ -63,7 +60,7 @@ def test_sql_injection(fixture_path, fixture_module, iast_span_defaults): @pytest.mark.parametrize("fixture_path,fixture_module", DDBBS) -def test_sql_injection_deduplication(fixture_path, fixture_module, iast_span_deduplication_enabled): +def test_sql_injection_deduplication(fixture_path, fixture_module, iast_context_deduplication_enabled): mod = _iast_patched_module(fixture_module) table = taint_pyobject( @@ -76,9 +73,6 @@ def test_sql_injection_deduplication(fixture_path, fixture_module, iast_span_ded for _ in range(0, 5): mod.sqli_simple(table) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - - assert span_report - data = span_report.build_and_scrub_value_parts() + data = _get_iast_data() assert len(data["vulnerabilities"]) == 1 VulnerabilityBase._prepare_report._reset_cache() diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index a4d1da049f8..faaa6fc0ee7 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -1,6 +1,5 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import origin_to_str @@ -13,14 +12,13 @@ from ddtrace.appsec._iast.reporter import Location from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection -from ddtrace.internal import core -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import _taint_pyobject_multiranges -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data from tests.utils import override_global_config # FIXME: ideally all these should pass, through the key is that we don't leak any potential PII - _ignore_list = {46, 47} @@ -28,7 +26,7 @@ "evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_SQL_INJECTION, ignore_list=_ignore_list)), ) -def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_span_defaults): +def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): with override_global_config(dict(_deduplication_enabled=False)): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], @@ -48,20 +46,16 @@ def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_ SqlInjection.report(tainted_object) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - - span_report.build_and_scrub_value_parts() - result = span_report._to_dict() - vulnerability = list(result["vulnerabilities"])[0] - source = list(result["sources"])[0] + data = _get_iast_data() + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_SQL_INJECTION assert source == sources_expected -def test_redacted_report_no_match(): +def test_redacted_report_no_match(iast_context_defaults): string_evicence = taint_pyobject( pyobject="SomeEvidenceValue", source_name="source_name", source_value="SomeEvidenceValue" ) @@ -81,7 +75,7 @@ def test_redacted_report_no_match(): assert v == {"name": "source_name", "origin": OriginType.PARAMETER, "value": "SomeEvidenceValue"} -def test_redacted_report_source_name_match(): +def test_redacted_report_source_name_match(iast_context_defaults): string_evicence = taint_pyobject(pyobject="'SomeEvidenceValue'", source_name="secret", source_value="SomeValue") ev = Evidence(value=string_evicence) loc = Location(path="foobar.py", line=35, spanId=123) @@ -99,7 +93,7 @@ def test_redacted_report_source_name_match(): assert v == {"name": "secret", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} -def test_redacted_report_source_value_match(): +def test_redacted_report_source_value_match(iast_context_defaults): string_evicence = taint_pyobject( pyobject="'SomeEvidenceValue'", source_name="SomeName", source_value="somepassword" ) @@ -119,7 +113,7 @@ def test_redacted_report_source_value_match(): assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghijkl", "redacted": True} -def test_redacted_report_evidence_value_match_also_redacts_source_value(): +def test_redacted_report_evidence_value_match_also_redacts_source_value(iast_context_defaults): string_evicence = taint_pyobject( pyobject="'SomeSecretPassword'", source_name="SomeName", source_value="SomeSecretPassword" ) @@ -144,7 +138,7 @@ def test_redacted_report_evidence_value_match_also_redacts_source_value(): } -def test_redacted_report_valueparts(): +def test_redacted_report_valueparts(iast_context_defaults): string_evicence = taint_pyobject(pyobject="1234", source_name="SomeName", source_value="SomeValue") ev = Evidence(value=add_aspect("SELECT * FROM users WHERE password = '", add_aspect(string_evicence, ":{SHA1}'"))) @@ -170,7 +164,7 @@ def test_redacted_report_valueparts(): assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} -def test_redacted_report_valueparts_username_not_tainted(): +def test_redacted_report_valueparts_username_not_tainted(iast_context_defaults): string_evicence = taint_pyobject(pyobject="secret", source_name="SomeName", source_value="SomeValue") string_tainted = add_aspect( @@ -201,7 +195,7 @@ def test_redacted_report_valueparts_username_not_tainted(): assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} -def test_redacted_report_valueparts_username_tainted(): +def test_redacted_report_valueparts_username_tainted(iast_context_defaults): string_evicence = taint_pyobject(pyobject="secret", source_name="SomeName", source_value="SomeValue") string_tainted = add_aspect( @@ -232,7 +226,7 @@ def test_redacted_report_valueparts_username_tainted(): assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} -def test_regression_ci_failure(): +def test_regression_ci_failure(iast_context_defaults): string_evicence = taint_pyobject(pyobject="master", source_name="SomeName", source_value="master") string_tainted = add_aspect( diff --git a/tests/appsec/iast/taint_sinks/test_ssrf.py b/tests/appsec/iast/taint_sinks/test_ssrf.py index c6655815a3b..8b35013b873 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf.py @@ -1,7 +1,3 @@ -import pytest - -from ddtrace.appsec._constants import IAST -from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect @@ -16,18 +12,17 @@ from ddtrace.contrib.urllib3.patch import unpatch as urllib3_unpatch from ddtrace.contrib.webbrowser.patch import patch as webbrowser_patch from ddtrace.contrib.webbrowser.patch import unpatch as webbrowser_unpatch -from ddtrace.internal import core +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data +from tests.appsec.iast.taint_sinks.conftest import _get_span_report from tests.utils import override_global_config FIXTURES_PATH = "tests/appsec/iast/taint_sinks/test_ssrf.py" -def setup(): - oce._enabled = True - - def _get_tainted_url(): tainted_path = taint_pyobject( pyobject="forbidden_dir/", @@ -38,8 +33,8 @@ def _get_tainted_url(): return add_aspect("http://localhost/", tainted_path), tainted_path -def _check_report(span_report, tainted_path, label): - data = span_report.build_and_scrub_value_parts() +def _check_report(tainted_path, label): + data = _get_iast_data() vulnerability = data["vulnerabilities"][0] source = data["sources"][0] @@ -61,7 +56,7 @@ def _check_report(span_report, tainted_path, label): assert vulnerability["hash"] == hash_value -def test_ssrf_requests(tracer, iast_span_defaults): +def test_ssrf_requests(tracer, iast_context_defaults): with override_global_config(dict(_iast_enabled=True)): requests_patch() try: @@ -75,14 +70,12 @@ def test_ssrf_requests(tracer, iast_span_defaults): except ConnectionError: pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_requests") + _check_report(tainted_path, "test_ssrf_requests") finally: requests_unpatch() -def test_ssrf_urllib3(tracer, iast_span_defaults): +def test_ssrf_urllib3(tracer, iast_context_defaults): with override_global_config(dict(_iast_enabled=True)): urllib3_patch() try: @@ -95,14 +88,12 @@ def test_ssrf_urllib3(tracer, iast_span_defaults): except urllib3.exceptions.HTTPError: pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_urllib3") + _check_report(tainted_path, "test_ssrf_urllib3") finally: urllib3_unpatch() -def test_ssrf_httplib(tracer, iast_span_defaults): +def test_ssrf_httplib(tracer, iast_context_defaults): with override_global_config(dict(_iast_enabled=True)): httplib_patch() try: @@ -117,14 +108,12 @@ def test_ssrf_httplib(tracer, iast_span_defaults): except ConnectionError: pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_httplib") + _check_report(tainted_path, "test_ssrf_httplib") finally: httplib_unpatch() -def test_ssrf_webbrowser(tracer, iast_span_defaults): +def test_ssrf_webbrowser(tracer, iast_context_defaults): with override_global_config(dict(_iast_enabled=True)): webbrowser_patch() try: @@ -137,14 +126,12 @@ def test_ssrf_webbrowser(tracer, iast_span_defaults): except ConnectionError: pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_webbrowser") + _check_report(tainted_path, "test_ssrf_webbrowser") finally: webbrowser_unpatch() -def test_urllib_request(tracer, iast_span_defaults): +def test_urllib_request(tracer, iast_context_defaults): with override_global_config(dict(_iast_enabled=True)): urllib_patch() try: @@ -157,14 +144,13 @@ def test_urllib_request(tracer, iast_span_defaults): except urllib.error.URLError: pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_urllib_request") + _check_report(tainted_path, "test_urllib_request") finally: urllib_unpatch() -def _check_no_report_if_deduplicated(span_report, num_vuln_expected): +def _check_no_report_if_deduplicated(num_vuln_expected): + span_report = _get_span_report() if num_vuln_expected == 0: assert span_report is None else: @@ -173,104 +159,114 @@ def _check_no_report_if_deduplicated(span_report, num_vuln_expected): assert len(span_report.vulnerabilities) == num_vuln_expected -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_ssrf_requests_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_ssrf_requests_deduplication(iast_context_deduplication_enabled): requests_patch() + _end_iast_context_and_oce() try: import requests from requests.exceptions import ConnectionError - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_ssrf_requests_deduplication - requests.get(tainted_url) - except ConnectionError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_requests_deduplication + requests.get(tainted_url) + except ConnectionError: + pass + + _check_no_report_if_deduplicated(num_vuln_expected) + _end_iast_context_and_oce() finally: requests_unpatch() -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_ssrf_urllib3_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_ssrf_urllib3_deduplication(iast_context_deduplication_enabled): urllib3_patch() + _end_iast_context_and_oce() try: - import urllib3 - - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_ssrf_urllib3_deduplication - urllib3.request(method="GET", url=tainted_url) - except urllib3.exceptions.HTTPError: - pass + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + import urllib3 - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_urllib3_deduplication + urllib3.request(method="GET", url=tainted_url) + except urllib3.exceptions.HTTPError: + pass + + _check_no_report_if_deduplicated(num_vuln_expected) + _end_iast_context_and_oce() finally: requests_unpatch() -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_ssrf_httplib_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_ssrf_httplib_deduplication(iast_context_deduplication_enabled): httplib_patch() + _end_iast_context_and_oce() try: import http.client - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - conn = http.client.HTTPConnection("127.0.0.1") - # label test_ssrf_httplib_deduplication - conn.request("GET", tainted_url) - conn.getresponse() - except ConnectionError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + conn = http.client.HTTPConnection("127.0.0.1") + # label test_ssrf_httplib_deduplication + conn.request("GET", tainted_url) + conn.getresponse() + except ConnectionError: + pass + + _check_no_report_if_deduplicated(num_vuln_expected) + _end_iast_context_and_oce() finally: httplib_unpatch() -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_ssrf_webbrowser_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_ssrf_webbrowser_deduplication(iast_context_deduplication_enabled): webbrowser_patch() + _end_iast_context_and_oce() try: import webbrowser - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_ssrf_webbrowser_deduplication - webbrowser.open(tainted_url) - except ConnectionError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_webbrowser_deduplication + webbrowser.open(tainted_url) + except ConnectionError: + pass + + _check_no_report_if_deduplicated(num_vuln_expected) + _end_iast_context_and_oce() finally: webbrowser_unpatch() -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_ssrf_urllib_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): +def test_ssrf_urllib_deduplication(iast_context_deduplication_enabled): urllib_patch() + _end_iast_context_and_oce() try: import urllib.request - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_urllib_request_deduplication - urllib.request.urlopen(tainted_url) - except urllib.error.URLError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_urllib_request_deduplication + urllib.request.urlopen(tainted_url) + except urllib.error.URLError: + pass + + _check_no_report_if_deduplicated(num_vuln_expected) + _end_iast_context_and_oce() finally: urllib_unpatch() diff --git a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py index aa329cb551e..256df8f079a 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py @@ -2,7 +2,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin from ddtrace.appsec._iast._taint_tracking import taint_pyobject @@ -13,9 +12,9 @@ from ddtrace.appsec._iast.reporter import Location from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.ssrf import SSRF -from ddtrace.internal import core -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import _taint_pyobject_multiranges -from tests.appsec.iast.taint_sinks.test_taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -24,7 +23,7 @@ @pytest.mark.parametrize( "evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_SSRF))[0:2] ) -def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_span_defaults): +def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): # TODO: fix get_parametrize(VULN_SSRF)[2:] replacements doesn't work correctly with params of SSRF tainted_object = evidence_input_value = evidence_input.get("value", "") if evidence_input_value: @@ -44,20 +43,16 @@ def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_ SSRF.report(tainted_object) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - - span_report.build_and_scrub_value_parts() - result = span_report._to_dict() - vulnerability = list(result["vulnerabilities"])[0] - source = list(result["sources"])[0] + data = _get_iast_data() + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_SSRF assert source == sources_expected -def test_ssrf_redact_param(): +def test_ssrf_redact_param(iast_context_defaults): password_taint_range = taint_pyobject(pyobject="test1234", source_name="password", source_value="test1234") ev = Evidence( @@ -85,7 +80,7 @@ def test_ssrf_redact_param(): ] -def test_cmdi_redact_user_password(): +def test_cmdi_redact_user_password(iast_context_defaults): user_taint_range = taint_pyobject(pyobject="root", source_name="username", source_value="root") password_taint_range = taint_pyobject( pyobject="superpasswordsecure", source_name="password", source_value="superpasswordsecure" diff --git a/tests/appsec/iast/taint_sinks/test_weak_cipher.py b/tests/appsec/iast/taint_sinks/test_weak_cipher.py index 13636012175..df90e435390 100644 --- a/tests/appsec/iast/taint_sinks/test_weak_cipher.py +++ b/tests/appsec/iast/taint_sinks/test_weak_cipher.py @@ -1,9 +1,10 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE from ddtrace.appsec._iast.taint_sinks.weak_cipher import unpatch_iast -from ddtrace.internal import core +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.appsec.iast.conftest import iast_context from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import cipher_arc2 from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import cipher_arc4 from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import cipher_blowfish @@ -11,11 +12,27 @@ from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import cipher_secure from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import cryptography_algorithm from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_span_report FIXTURES_PATH = "tests/appsec/iast/fixtures/taint_sinks/weak_algorithms.py" +@pytest.fixture +def iast_context_des_rc2_configured(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="DES, RC2")) + + +@pytest.fixture +def iast_context_rc4_configured(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="RC4")) + + +@pytest.fixture +def iast_context_blowfish_configured(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_CIPHER_ALGORITHMS="BLOWFISH, RC2")) + + @pytest.mark.parametrize( "mode,cipher_func", [ @@ -25,11 +42,11 @@ ("MODE_OFB", "DES_OfbMode"), ], ) -def test_weak_cipher_crypto_des(iast_span_defaults, mode, cipher_func): +def test_weak_cipher_crypto_des(iast_context_defaults, mode, cipher_func): from Crypto.Cipher import DES cipher_des(mode=getattr(DES, mode)) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash("cipher_des", VULN_WEAK_CIPHER_TYPE, filename=FIXTURES_PATH) vulnerabilities = list(span_report.vulnerabilities) @@ -49,11 +66,11 @@ def test_weak_cipher_crypto_des(iast_span_defaults, mode, cipher_func): ("MODE_OFB", "Blowfish_OfbMode"), ], ) -def test_weak_cipher_crypto_blowfish(iast_span_defaults, mode, cipher_func): +def test_weak_cipher_crypto_blowfish(iast_context_defaults, mode, cipher_func): from Crypto.Cipher import Blowfish cipher_blowfish(mode=getattr(Blowfish, mode)) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash("cipher_blowfish", VULN_WEAK_CIPHER_TYPE, filename=FIXTURES_PATH) vulnerabilities = list(span_report.vulnerabilities) @@ -73,11 +90,11 @@ def test_weak_cipher_crypto_blowfish(iast_span_defaults, mode, cipher_func): ("MODE_OFB", "RC2_OfbMode"), ], ) -def test_weak_cipher_rc2(iast_span_defaults, mode, cipher_func): +def test_weak_cipher_rc2(mode, cipher_func, iast_context_defaults): from Crypto.Cipher import ARC2 cipher_arc2(mode=getattr(ARC2, mode)) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash("cipher_arc2", VULN_WEAK_CIPHER_TYPE, filename=FIXTURES_PATH) vulnerabilities = list(span_report.vulnerabilities) @@ -88,9 +105,9 @@ def test_weak_cipher_rc2(iast_span_defaults, mode, cipher_func): assert vulnerabilities[0].evidence.value == cipher_func -def test_weak_cipher_rc4(iast_span_defaults): +def test_weak_cipher_rc4(iast_context_defaults): cipher_arc4() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash("cipher_arc4", VULN_WEAK_CIPHER_TYPE, filename=FIXTURES_PATH) vulnerabilities = list(span_report.vulnerabilities) @@ -109,10 +126,9 @@ def test_weak_cipher_rc4(iast_span_defaults): ("IDEA", "idea"), ], ) -def test_weak_cipher_cryptography_blowfish(iast_span_defaults, algorithm, cipher_func): +def test_weak_cipher_cryptography_blowfish(iast_context_defaults, algorithm, cipher_func): cryptography_algorithm(algorithm) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - + span_report = _get_span_report() line, hash_value = get_line_and_hash("cryptography_algorithm", VULN_WEAK_CIPHER_TYPE, filename=FIXTURES_PATH) vulnerabilities = list(span_report.vulnerabilities) @@ -122,77 +138,80 @@ def test_weak_cipher_cryptography_blowfish(iast_span_defaults, algorithm, cipher assert vulnerabilities[0].hash == hash_value -def test_weak_cipher_blowfish__des_rc2_configured(iast_span_des_rc2_configured): +def test_weak_cipher_blowfish__des_rc2_configured(iast_context_des_rc2_configured): from Crypto.Cipher import Blowfish cipher_blowfish(Blowfish.MODE_CBC) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_des_rc2_configured) + span_report = _get_span_report() assert span_report is None -def test_weak_cipher_rc2__rc4_configured(iast_span_rc4_configured): +def test_weak_cipher_rc2__rc4_configured(iast_context_rc4_configured): from Crypto.Cipher import ARC2 cipher_arc2(mode=ARC2.MODE_CBC) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_rc4_configured) + span_report = _get_span_report() assert span_report is None -def test_weak_cipher_cryptography_rc4_configured(iast_span_rc4_configured): +def test_weak_cipher_cryptography_rc4_configured(iast_context_rc4_configured): cryptography_algorithm("ARC4") - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_rc4_configured) + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) assert vulnerabilities[0].type == VULN_WEAK_CIPHER_TYPE -def test_weak_cipher_cryptography_blowfish__rc4_configured(iast_span_rc4_configured): +def test_weak_cipher_cryptography_blowfish__rc4_configured(iast_context_rc4_configured): cryptography_algorithm("Blowfish") - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_rc4_configured) + span_report = _get_span_report() assert span_report is None -def test_weak_cipher_cryptography_blowfish_configured(iast_span_blowfish_configured): +def test_weak_cipher_cryptography_blowfish_configured(iast_context_blowfish_configured): cryptography_algorithm("Blowfish") - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_blowfish_configured) + span_report = _get_span_report() vulnerabilities = list(span_report.vulnerabilities) assert vulnerabilities[0].type == VULN_WEAK_CIPHER_TYPE -def test_weak_cipher_rc4_unpatched(iast_span_defaults): +def test_weak_cipher_rc4_unpatched(iast_context_defaults): unpatch_iast() cipher_arc4() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None -@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) -def test_weak_cipher_deduplication(num_vuln_expected, iast_span_deduplication_enabled): - for _ in range(0, 5): - cryptography_algorithm("Blowfish") +def test_weak_cipher_deduplication(iast_context_deduplication_enabled): + _end_iast_context_and_oce() + for num_vuln_expected in [1, 0, 0]: + _start_iast_context_and_oce() + for _ in range(0, 5): + cryptography_algorithm("Blowfish") - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + span_report = _get_span_report() - if num_vuln_expected == 0: - assert span_report is None - else: - assert span_report + if num_vuln_expected == 0: + assert span_report is None + else: + assert span_report - assert len(span_report.vulnerabilities) == num_vuln_expected + assert len(span_report.vulnerabilities) == num_vuln_expected + _end_iast_context_and_oce() -def test_weak_cipher_secure(iast_span_defaults): +def test_weak_cipher_secure(iast_context_defaults): cipher_secure() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None -def test_weak_cipher_secure_multiple_calls_error(iast_span_defaults): +def test_weak_cipher_secure_multiple_calls_error(iast_context_defaults): for _ in range(50): cipher_secure() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None diff --git a/tests/appsec/iast/taint_sinks/test_weak_hash.py b/tests/appsec/iast/taint_sinks/test_weak_hash.py index 13969d932b1..603a78218fe 100644 --- a/tests/appsec/iast/taint_sinks/test_weak_hash.py +++ b/tests/appsec/iast/taint_sinks/test_weak_hash.py @@ -1,21 +1,67 @@ +from contextlib import contextmanager import sys from mock import mock import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.constants import VULN_INSECURE_HASHING_TYPE from ddtrace.appsec._iast.taint_sinks.weak_hash import unpatch_iast -from ddtrace.internal import core +from tests.appsec.iast.conftest import iast_context from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import hashlib_new from tests.appsec.iast.fixtures.taint_sinks.weak_algorithms import parametrized_weak_hash from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_span_report WEAK_ALGOS_FIXTURES_PATH = "tests/appsec/iast/fixtures/taint_sinks/weak_algorithms.py" WEAK_HASH_FIXTURES_PATH = "tests/appsec/iast/taint_sinks/test_weak_hash.py" +@pytest.fixture +def iast_context_md5_and_sha1_configured(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD5, SHA1")) + + +@pytest.fixture +def iast_context_only_md4(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD4")) + + +@pytest.fixture +def iast_context_only_md5(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="MD5")) + + +@pytest.fixture +def iast_context_only_sha1(): + yield from iast_context(dict(DD_IAST_ENABLED="true", DD_IAST_WEAK_HASH_ALGORITHMS="SHA1")) + + +@pytest.fixture +def iast_context_contextmanager_deduplication_enabled(): + from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase + + def iast_aux(deduplication_enabled=True, time_lapse=3600.0, max_vulns=10): + from ddtrace.appsec._deduplications import deduplication + from ddtrace.appsec._iast.taint_sinks.weak_hash import WeakHash + + try: + WeakHash._vulnerability_quota = max_vulns + old_value = deduplication._time_lapse + deduplication._time_lapse = time_lapse + yield from iast_context(dict(DD_IAST_ENABLED="true"), deduplication=deduplication_enabled) + finally: + deduplication._time_lapse = old_value + del WeakHash._vulnerability_quota + + try: + # Yield a context manager allowing to create several spans to test deduplication + yield contextmanager(iast_aux) + finally: + # Reset the cache to avoid side effects in other tests + VulnerabilityBase._prepare_report._reset_cache() + + @pytest.mark.parametrize( "hash_func,method", [ @@ -25,14 +71,14 @@ ("sha1", "hexdigest"), ], ) -def test_weak_hash_hashlib(iast_span_defaults, hash_func, method): +def test_weak_hash_hashlib(iast_context_defaults, hash_func, method): parametrized_weak_hash(hash_func, method) line, hash_value = get_line_and_hash( "parametrized_weak_hash", VULN_INSECURE_HASHING_TYPE, filename=WEAK_ALGOS_FIXTURES_PATH ) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_ALGOS_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].location.line == line @@ -48,7 +94,7 @@ def test_weak_hash_hashlib(iast_span_defaults, hash_func, method): ("md5", "hexdigest", -1), ], ) -def test_ensure_line_reported_is_minus_one_for_edge_cases(iast_span_defaults, hash_func, method, fake_line): +def test_ensure_line_reported_is_minus_one_for_edge_cases(iast_context_defaults, hash_func, method, fake_line): with mock.patch( "ddtrace.appsec._iast.taint_sinks._base.get_info_frame", return_value=(WEAK_ALGOS_FIXTURES_PATH, fake_line) ): @@ -58,7 +104,7 @@ def test_ensure_line_reported_is_minus_one_for_edge_cases(iast_span_defaults, ha "parametrized_weak_hash", VULN_INSECURE_HASHING_TYPE, filename=WEAK_ALGOS_FIXTURES_PATH, fixed_line=-1 ) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_ALGOS_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].location.line == -1 @@ -67,32 +113,32 @@ def test_ensure_line_reported_is_minus_one_for_edge_cases(iast_span_defaults, ha @pytest.mark.parametrize("hash_func", ["md5", "sha1"]) -def test_weak_hash_hashlib_no_digest(iast_span_md5_and_sha1_configured, hash_func): +def test_weak_hash_hashlib_no_digest(iast_context_md5_and_sha1_configured, hash_func): import hashlib m = getattr(hashlib, hash_func)() m.update(b"Nobody inspects") m.update(b" the spammish repetition") - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_md5_and_sha1_configured) + span_report = _get_span_report() assert span_report is None @pytest.mark.parametrize("hash_func,method", [("sha256", "digest"), ("sha256", "hexdigest")]) -def test_weak_hash_secure_hash(iast_span_md5_and_sha1_configured, hash_func, method): +def test_weak_hash_secure_hash(iast_context_md5_and_sha1_configured, hash_func, method): import hashlib m = getattr(hashlib, hash_func)() m.update(b"Nobody inspects") m.update(b" the spammish repetition") getattr(m, method)() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_md5_and_sha1_configured) + span_report = _get_span_report() assert span_report is None -def test_weak_hash_new(iast_span_defaults): +def test_weak_hash_new(iast_context_defaults): hashlib_new() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash("hashlib_new", VULN_INSECURE_HASHING_TYPE, filename=WEAK_ALGOS_FIXTURES_PATH) assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE @@ -102,29 +148,7 @@ def test_weak_hash_new(iast_span_defaults): assert list(span_report.vulnerabilities)[0].hash == hash_value -def test_weak_hash_new_with_child_span(tracer, iast_span_defaults): - with tracer.trace("test_child") as span: - hashlib_new() - span_report1 = core.get_item(IAST.CONTEXT_KEY, span=span) - - span_report2 = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - - line, hash_value = get_line_and_hash("hashlib_new", VULN_INSECURE_HASHING_TYPE, filename=WEAK_ALGOS_FIXTURES_PATH) - - assert list(span_report1.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE - assert list(span_report1.vulnerabilities)[0].location.path == WEAK_ALGOS_FIXTURES_PATH - assert list(span_report1.vulnerabilities)[0].evidence.value == "md5" - - assert list(span_report1.vulnerabilities)[0].hash == hash_value - - assert list(span_report2.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE - assert list(span_report2.vulnerabilities)[0].location.path == WEAK_ALGOS_FIXTURES_PATH - assert list(span_report2.vulnerabilities)[0].evidence.value == "md5" - - assert list(span_report2.vulnerabilities)[0].hash == hash_value - - -def test_weak_hash_md5_builtin_py3_unpatched(iast_span_md5_and_sha1_configured): +def test_weak_hash_md5_builtin_py3_unpatched(iast_context_md5_and_sha1_configured): import _md5 unpatch_iast() @@ -132,117 +156,117 @@ def test_weak_hash_md5_builtin_py3_unpatched(iast_span_md5_and_sha1_configured): m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_md5_and_sha1_configured) + span_report = _get_span_report() assert span_report is None -def test_weak_hash_md5_builtin_py3_md5_and_sha1_configured(iast_span_defaults): +def test_weak_hash_md5_builtin_py3_md5_and_sha1_configured(iast_context_defaults): import _md5 m = _md5.md5() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_HASH_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].evidence.value == "md5" -def test_weak_hash_md5_builtin_py3_only_md4_configured(iast_span_only_md4): +def test_weak_hash_md5_builtin_py3_only_md4_configured(iast_context_only_md4): import _md5 m = _md5.md5() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_only_md4) + span_report = _get_span_report() assert span_report is None -def test_weak_hash_md5_builtin_py3_only_md5_configured(iast_span_only_md5): +def test_weak_hash_md5_builtin_py3_only_md5_configured(iast_context_only_md5): import _md5 m = _md5.md5() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_only_md5) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_HASH_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].evidence.value == "md5" -def test_weak_hash_md5_builtin_py3_only_sha1_configured(iast_span_only_sha1): +def test_weak_hash_md5_builtin_py3_only_sha1_configured(iast_context_only_sha1): import _md5 m = _md5.md5() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_only_sha1) + span_report = _get_span_report() assert span_report is None -def test_weak_hash_pycryptodome_hashes_md5(iast_span_defaults): +def test_weak_hash_pycryptodome_hashes_md5(iast_context_defaults): from Crypto.Hash import MD5 m = MD5.new() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_HASH_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].evidence.value == "md5" -def test_weak_hash_pycryptodome_hashes_sha1_defaults(iast_span_defaults): +def test_weak_hash_pycryptodome_hashes_sha1_defaults(iast_context_defaults): from Crypto.Hash import SHA1 m = SHA1.new() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_HASH_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].evidence.value == "sha1" -def test_weak_hash_pycryptodome_hashes_sha1_only_md5_configured(iast_span_only_md5): +def test_weak_hash_pycryptodome_hashes_sha1_only_md5_configured(iast_context_only_md5): from Crypto.Hash import SHA1 m = SHA1.new() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_only_md5) + span_report = _get_span_report() assert span_report is None -def test_weak_hash_pycryptodome_hashes_sha1_only_sha1_configured(iast_span_only_sha1): +def test_weak_hash_pycryptodome_hashes_sha1_only_sha1_configured(iast_context_only_sha1): from Crypto.Hash import SHA1 m = SHA1.new() m.update(b"Nobody inspects") m.update(b" the spammish repetition") m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_only_sha1) + span_report = _get_span_report() assert list(span_report.vulnerabilities)[0].type == VULN_INSECURE_HASHING_TYPE assert list(span_report.vulnerabilities)[0].location.path == WEAK_HASH_FIXTURES_PATH assert list(span_report.vulnerabilities)[0].evidence.value == "sha1" -def test_weak_check_repeated(iast_span_defaults): +def test_weak_check_repeated(iast_context_defaults): import hashlib m = hashlib.new("md5") @@ -252,36 +276,36 @@ def test_weak_check_repeated(iast_span_defaults): for _ in range(0, num_vulnerabilities): m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert len(span_report.vulnerabilities) == 1 @pytest.mark.skipif(sys.version_info > (3, 10, 0), reason="hmac has a weak hash vulnerability until Python 3.10") -def test_weak_hash_check_hmac(iast_span_defaults): +def test_weak_hash_check_hmac(iast_context_defaults): import hashlib import hmac mac = hmac.new(b"test", digestmod=hashlib.md5) mac.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert len(span_report.vulnerabilities) == 1 -def test_weak_check_hmac_secure(iast_span_defaults): +def test_weak_check_hmac_secure(iast_context_defaults): import hashlib import hmac mac = hmac.new(b"test", digestmod=hashlib.sha256) mac.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @pytest.mark.parametrize("deduplication_enabled", (False, True)) @pytest.mark.parametrize("time_lapse", (3600.0, 0.001)) def test_weak_hash_deduplication_expired_cache( - iast_context_span_deduplication_enabled, deduplication_enabled, time_lapse + iast_context_contextmanager_deduplication_enabled, deduplication_enabled, time_lapse ): """ Test deduplication enabled/disabled over several spans @@ -291,13 +315,13 @@ def test_weak_hash_deduplication_expired_cache( import time for i in range(10): - with iast_context_span_deduplication_enabled(deduplication_enabled, time_lapse) as iast_span_defaults: + with iast_context_contextmanager_deduplication_enabled(deduplication_enabled, time_lapse): time.sleep(0.002) m = hashlib.new("md5") m.update(b"Nobody inspects" * i) m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() if i and deduplication_enabled and time_lapse > 0.2: assert span_report is None, f"Failed at iteration {i}" else: diff --git a/tests/appsec/iast/taint_sinks/test_weak_randomness.py b/tests/appsec/iast/taint_sinks/test_weak_randomness.py index f8aa0ab1a71..f3a586e41ca 100644 --- a/tests/appsec/iast/taint_sinks/test_weak_randomness.py +++ b/tests/appsec/iast/taint_sinks/test_weak_randomness.py @@ -2,12 +2,11 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.constants import DEFAULT_WEAK_RANDOMNESS_FUNCTIONS from ddtrace.appsec._iast.constants import VULN_WEAK_RANDOMNESS -from ddtrace.internal import core from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_span_report FIXTURES_RANDOM_PATH = "tests/appsec/iast/fixtures/taint_sinks/weak_randomness_random.py" @@ -23,11 +22,11 @@ "random_func", DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, ) -def test_weak_randomness(random_func, iast_span_defaults): +def test_weak_randomness(random_func, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.weak_randomness_random") getattr(mod, "random_{}".format(random_func))() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash( "weak_randomness_{}".format(random_func), VULN_WEAK_RANDOMNESS, filename=FIXTURES_RANDOM_PATH ) @@ -42,11 +41,11 @@ def test_weak_randomness(random_func, iast_span_defaults): @pytest.mark.skipif(WEEK_RANDOMNESS_PY_VERSION, reason="Some random methods exists on 3.9 or higher") -def test_weak_randomness_no_dynamic_import(iast_span_defaults): +def test_weak_randomness_no_dynamic_import(iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.weak_randomness_random") mod.random_dynamic_import() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @@ -55,11 +54,11 @@ def test_weak_randomness_no_dynamic_import(iast_span_defaults): "random_func", DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, ) -def test_weak_randomness_module(random_func, iast_span_defaults): +def test_weak_randomness_module(random_func, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.weak_randomness_random_module") getattr(mod, "random_{}".format(random_func))() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() line, hash_value = get_line_and_hash( "weak_randomness_{}".format(random_func), VULN_WEAK_RANDOMNESS, filename=FIXTURES_RANDOM_MODULE_PATH ) @@ -78,18 +77,18 @@ def test_weak_randomness_module(random_func, iast_span_defaults): "random_func", DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, ) -def test_weak_randomness_secure_module(random_func, iast_span_defaults): +def test_weak_randomness_secure_module(random_func, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.weak_randomness_random_secure_module") getattr(mod, "random_{}".format(random_func))() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @pytest.mark.skipif(WEEK_RANDOMNESS_PY_VERSION, reason="Some random methods exists on 3.9 or higher") -def test_weak_randomness_secrets_secure_package(iast_span_defaults): +def test_weak_randomness_secrets_secure_package(iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.weak_randomness_secrets") mod.random_choice() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None diff --git a/tests/appsec/iast/taint_tracking/conftest.py b/tests/appsec/iast/taint_tracking/conftest.py new file mode 100644 index 00000000000..e1791e58ef2 --- /dev/null +++ b/tests/appsec/iast/taint_tracking/conftest.py @@ -0,0 +1,15 @@ +import pytest + +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.utils import override_env +from tests.utils import override_global_config + + +@pytest.fixture(autouse=True) +def iast_create_context(): + env = {"DD_IAST_REQUEST_SAMPLING": "100"} + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + _start_iast_context_and_oce() + yield + _end_iast_context_and_oce() diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range.py b/tests/appsec/iast/taint_tracking/test_native_taint_range.py index 090360ed8e1..e676324d63d 100644 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range.py +++ b/tests/appsec/iast/taint_tracking/test_native_taint_range.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from ast import literal_eval import asyncio +import logging from multiprocessing.pool import ThreadPool import random import re @@ -9,7 +10,7 @@ import pytest -from ddtrace.appsec._iast import oce +from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -32,10 +33,7 @@ from ddtrace.appsec._iast._taint_tracking.aspects import format_aspect from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect from tests.appsec.iast.conftest import IAST_VALID_LOG - - -def setup(): - oce._enabled = True +from tests.utils import override_env def test_source_origin_refcount(): @@ -444,7 +442,6 @@ def test_reset_objects(): def reset_context_loop(): - create_context() a_1 = "abc123" for i in range(25): a_1 = taint_pyobject( @@ -454,7 +451,6 @@ def reset_context_loop(): source_origin=OriginType.PARAMETER, ) sleep(0.01) - reset_context() def reset_contexts_loop(): @@ -482,7 +478,7 @@ async def async_reset_contexts_loop(task_id: int): return reset_contexts_loop() -def test_race_conditions_reset_context_threads(caplog, telemetry_writer): +def test_race_conditions_threads(caplog, telemetry_writer): """we want to validate context is working correctly among multiple request and no race condition creating and destroying contexts """ @@ -499,21 +495,21 @@ def test_race_conditions_reset_context_threads(caplog, telemetry_writer): assert len(list_metrics_logs) == 0 +@pytest.mark.skip_iast_check_logs def test_race_conditions_reset_contexts_threads(caplog, telemetry_writer): """we want to validate context is working correctly among multiple request and no race condition creating and destroying contexts """ - pool = ThreadPool(processes=3) - results_async = [pool.apply_async(reset_contexts_loop) for _ in range(70)] - _ = [res.get() for res in results_async] + with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + pool = ThreadPool(processes=3) + results_async = [pool.apply_async(reset_contexts_loop) for _ in range(70)] + _ = [res.get() for res in results_async] - log_messages = [record.message for record in caplog.get_records("call")] - for message in log_messages: - if IAST_VALID_LOG.search(message): - pytest.fail(message) - - list_metrics_logs = list(telemetry_writer._logs) - assert len(list_metrics_logs) == 0 + log_messages = [record.message for record in caplog.get_records("call")] + if not any(IAST_VALID_LOG.search(message) for message in log_messages): + pytest.fail("All contexts reset but no fail") + list_metrics_logs = list(telemetry_writer._logs) + assert len(list_metrics_logs) == 0 @pytest.mark.asyncio @@ -540,7 +536,7 @@ async def test_race_conditions_reset_contexs_async(caplog, telemetry_writer): """we want to validate context is working correctly among multiple request and no race condition creating and destroying contexts """ - tasks = [async_reset_contexts_loop(i) for i in range(50)] + tasks = [async_reset_contexts_loop(i) for i in range(20)] results = await asyncio.gather(*tasks) assert results diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index 731b40b9f28..7878bf3045e 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -4,7 +4,6 @@ import pytest from ddtrace.appsec._constants import IAST -from ddtrace.appsec._iast import oce from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Source from tests.utils import override_env @@ -20,10 +19,6 @@ from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect -def setup(): - oce._enabled = True - - def test_taint_ranges_as_evidence_info_nothing_tainted(): text = "nothing tainted" sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(text) diff --git a/tests/appsec/iast/test_grpc_iast.py b/tests/appsec/iast/test_grpc_iast.py index afa904f9432..1739d89aeda 100644 --- a/tests/appsec/iast/test_grpc_iast.py +++ b/tests/appsec/iast/test_grpc_iast.py @@ -3,6 +3,7 @@ import grpc from grpc._grpcio_metadata import __version__ as _GRPC_VERSION import mock +import pytest from ddtrace.appsec._constants import SPAN_DATA_NAMES from tests.contrib.grpc.common import GrpcBaseTestCase @@ -14,11 +15,18 @@ from tests.utils import override_env from tests.utils import override_global_config +from .conftest import iast_context + _GRPC_PORT = 50531 _GRPC_VERSION = tuple([int(i) for i in _GRPC_VERSION.split(".")]) +@pytest.fixture(autouse=True) +def iast_c_context(): + yield from iast_context(dict(DD_IAST_ENABLED="true")) + + def _check_test_range(value): from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges @@ -30,7 +38,7 @@ def _check_test_range(value): class GrpcTestIASTCase(GrpcBaseTestCase): - @flaky(1735812000, reason="IAST context refactor breaks grpc") + @flaky(1735812000, reason="IAST context refactor breaks grpc. APPSEC-55239") @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_IAST_ENABLED="1")) def test_taint_iast_single(self): with override_env({"DD_IAST_ENABLED": "True"}): @@ -50,17 +58,17 @@ def test_taint_iast_single_server(self): assert hasattr(res, "message") _check_test_range(res.message) + @flaky(1735812000, reason="IAST context refactor breaks grpc. APPSEC-55239") @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_IAST_ENABLED="1")) def test_taint_iast_twice(self): - with override_env({"DD_IAST_ENABLED": "True"}): - with self.override_config("grpc", dict(service_name="myclientsvc")): - with self.override_config("grpc_server", dict(service_name="myserversvc")): - with grpc.insecure_channel("localhost:%d" % (_GRPC_PORT)) as channel1: - stub1 = HelloStub(channel1) - responses_iterator = stub1.SayHelloTwice(HelloRequest(name="test")) - for res in responses_iterator: - assert hasattr(res, "message") - _check_test_range(res.message) + with self.override_config("grpc", dict(service_name="myclientsvc")): + with self.override_config("grpc_server", dict(service_name="myserversvc")): + with grpc.insecure_channel("localhost:%d" % (_GRPC_PORT)) as channel1: + stub1 = HelloStub(channel1) + responses_iterator = stub1.SayHelloTwice(HelloRequest(name="test")) + for res in responses_iterator: + assert hasattr(res, "message") + _check_test_range(res.message) def test_taint_iast_twice_server(self): # use an event to signal when the callbacks have been called from the response @@ -80,6 +88,7 @@ def callback(response): callback_called.wait(timeout=1) + @flaky(1735812000, reason="IAST context refactor breaks grpc. APPSEC-55239") @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_IAST_ENABLED="1")) def test_taint_iast_repeatedly(self): with override_env({"DD_IAST_ENABLED": "True"}): @@ -116,7 +125,7 @@ def callback(response): callback_called.wait(timeout=1) - @flaky(1735812000, reason="IAST context refactor breaks grpc") + @flaky(1735812000, reason="IAST context refactor breaks grpc. APPSEC-55239") @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_IAST_ENABLED="1")) def test_taint_iast_last(self): with override_env({"DD_IAST_ENABLED": "True"}): diff --git a/tests/appsec/iast/test_iast_propagation_path.py b/tests/appsec/iast/test_iast_propagation_path.py index 9637b692501..c9c32b7258e 100644 --- a/tests/appsec/iast/test_iast_propagation_path.py +++ b/tests/appsec/iast/test_iast_propagation_path.py @@ -1,13 +1,12 @@ from mock.mock import ANY import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL -from ddtrace.internal import core from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_span_report FIXTURES_PATH = "tests/appsec/iast/fixtures/propagation_path.py" @@ -27,14 +26,14 @@ def _assert_vulnerability(data, value_parts, file_line_label): assert vulnerability["hash"] == hash_value -def test_propagation_no_path(iast_span_defaults): +def test_propagation_no_path(iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") origin1 = "taintsource" tainted_string = taint_pyobject(origin1, source_name="path", source_value=origin1, source_origin=OriginType.PATH) for i in range(100): mod.propagation_no_path(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report is None @@ -48,13 +47,13 @@ def test_propagation_no_path(iast_span_defaults): (bytearray(b"taintsource1")), ], ) -def test_propagation_path_1_origin_1_propagation(origin1, iast_span_defaults): +def test_propagation_path_1_origin_1_propagation(origin1, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") tainted_string = taint_pyobject(origin1, source_name="path", source_value=origin1, source_origin=OriginType.PATH) mod.propagation_path_1_source_1_prop(tainted_string) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() span_report.build_and_scrub_value_parts() data = span_report._to_dict() sources = data["sources"] @@ -82,14 +81,14 @@ def test_propagation_path_1_origin_1_propagation(origin1, iast_span_defaults): bytearray(b"taintsource1"), ], ) -def test_propagation_path_1_origins_2_propagations(origin1, iast_span_defaults): +def test_propagation_path_1_origins_2_propagations(origin1, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") tainted_string_1 = taint_pyobject(origin1, source_name="path1", source_value=origin1, source_origin=OriginType.PATH) mod.propagation_path_1_source_2_prop(tainted_string_1) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() span_report.build_and_scrub_value_parts() data = span_report._to_dict() sources = data["sources"] @@ -126,7 +125,7 @@ def test_propagation_path_1_origins_2_propagations(origin1, iast_span_defaults): (b"taintsource1", bytearray(b"taintsource2")), ], ) -def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_span_defaults): +def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") tainted_string_1 = taint_pyobject(origin1, source_name="path1", source_value=origin1, source_origin=OriginType.PATH) @@ -135,7 +134,7 @@ def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_span_d ) mod.propagation_path_2_source_2_prop(tainted_string_1, tainted_string_2) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() span_report.build_and_scrub_value_parts() data = span_report._to_dict() sources = data["sources"] @@ -177,7 +176,7 @@ def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_span_d (b"taintsource1", bytearray(b"taintsource2")), ], ) -def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_span_defaults): +def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") tainted_string_1 = taint_pyobject(origin1, source_name="path1", source_value=origin1, source_origin=OriginType.PATH) @@ -186,7 +185,7 @@ def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_span_de ) mod.propagation_path_3_prop(tainted_string_1, tainted_string_2) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() span_report.build_and_scrub_value_parts() data = span_report._to_dict() sources = data["sources"] @@ -233,7 +232,7 @@ def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_span_de (b"taintsource1", bytearray(b"taintsource2")), ], ) -def test_propagation_path_2_origins_5_propagation(origin1, origin2, iast_span_defaults): +def test_propagation_path_2_origins_5_propagation(origin1, origin2, iast_context_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") tainted_string_1 = taint_pyobject(origin1, source_name="path1", source_value=origin1, source_origin=OriginType.PATH) @@ -242,7 +241,7 @@ def test_propagation_path_2_origins_5_propagation(origin1, origin2, iast_span_de ) mod.propagation_path_5_prop(tainted_string_1, tainted_string_2) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() span_report.build_and_scrub_value_parts() data = span_report._to_dict() sources = data["sources"] diff --git a/tests/appsec/iast/test_json_tainting.py b/tests/appsec/iast/test_json_tainting.py index 88053170bff..2678fd70487 100644 --- a/tests/appsec/iast/test_json_tainting.py +++ b/tests/appsec/iast/test_json_tainting.py @@ -2,7 +2,6 @@ import pytest -from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted @@ -14,7 +13,6 @@ def setup(): create_context() - oce._enabled = True FIXTURES_PATH = "tests/appsec/iast/fixtures/weak_algorithms.py" @@ -40,7 +38,7 @@ def is_fully_tainted(obj): @pytest.mark.parametrize("input_jsonstr, res_type, tainted_type", TEST_INPUTS) -def test_taint_json(iast_span_defaults, input_jsonstr, res_type, tainted_type): +def test_taint_json(iast_context_defaults, input_jsonstr, res_type, tainted_type): assert json._datadog_json_tainting_patch with override_global_config(dict(_iast_enabled=True)): input_str = taint_pyobject( @@ -57,7 +55,7 @@ def test_taint_json(iast_span_defaults, input_jsonstr, res_type, tainted_type): @pytest.mark.parametrize("input_jsonstr, res_type, tainted_type", TEST_INPUTS) -def test_taint_json_no_taint(iast_span_defaults, input_jsonstr, res_type, tainted_type): +def test_taint_json_no_taint(iast_context_defaults, input_jsonstr, res_type, tainted_type): with override_global_config(dict(_iast_enabled=True)): input_str = input_jsonstr assert not is_pyobject_tainted(input_str) diff --git a/tests/appsec/iast/test_overhead_control_engine.py b/tests/appsec/iast/test_overhead_control_engine.py index 8f64ff8a5c6..c0ed40bb041 100644 --- a/tests/appsec/iast/test_overhead_control_engine.py +++ b/tests/appsec/iast/test_overhead_control_engine.py @@ -1,10 +1,14 @@ +import logging from time import sleep +import pytest + from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import get_iast_reporter from ddtrace.appsec._iast._overhead_control_engine import MAX_REQUESTS from ddtrace.appsec._iast._overhead_control_engine import MAX_VULNERABILITIES_PER_REQUEST -from ddtrace.internal import core +from tests.utils import override_env def function_with_vulnerabilities_3(tracer): @@ -40,7 +44,8 @@ def function_with_vulnerabilities_1(tracer): return 1 -def test_oce_max_vulnerabilities_per_request(iast_span_defaults): +@pytest.mark.skip_iast_check_logs +def test_oce_max_vulnerabilities_per_request(iast_context_defaults): import hashlib m = hashlib.md5() @@ -49,12 +54,13 @@ def test_oce_max_vulnerabilities_per_request(iast_span_defaults): m.digest() m.digest() m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = get_iast_reporter() assert len(span_report.vulnerabilities) == MAX_VULNERABILITIES_PER_REQUEST -def test_oce_reset_vulnerabilities_report(iast_span_defaults): +@pytest.mark.skip_iast_check_logs +def test_oce_reset_vulnerabilities_report(iast_context_defaults): import hashlib m = hashlib.md5() @@ -65,12 +71,13 @@ def test_oce_reset_vulnerabilities_report(iast_span_defaults): oce.vulnerabilities_reset_quota() m.digest() - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = get_iast_reporter() assert len(span_report.vulnerabilities) == MAX_VULNERABILITIES_PER_REQUEST + 1 -def test_oce_no_race_conditions(tracer, iast_span_defaults): +@pytest.mark.skip_iast_check_logs +def test_oce_no_race_conditions_in_span(iast_span_defaults): from ddtrace.appsec._iast._overhead_control_engine import OverheadControl oc = OverheadControl() @@ -120,7 +127,7 @@ def test_oce_no_race_conditions(tracer, iast_span_defaults): assert oc._request_quota == 0 -def acquire_and_release_quota(oc, iast_span_defaults): +def acquire_and_release_quota_in_spans(oc, iast_span_defaults): """ Just acquires the request quota and releases it with some random sleeps @@ -135,7 +142,8 @@ def acquire_and_release_quota(oc, iast_span_defaults): oc.release_request() -def test_oce_concurrent_requests(tracer, iast_span_defaults): +@pytest.mark.skip_iast_check_logs +def test_oce_concurrent_requests_in_spans(iast_span_defaults): """ Ensures quota is always within bounds after multithreading scenario """ @@ -151,7 +159,7 @@ def test_oce_concurrent_requests(tracer, iast_span_defaults): num_requests = 5000 threads = [ - threading.Thread(target=acquire_and_release_quota, args=(oc, iast_span_defaults)) + threading.Thread(target=acquire_and_release_quota_in_spans, args=(oc, iast_span_defaults)) for _ in range(0, num_requests) ] for thread in threads: @@ -161,3 +169,29 @@ def test_oce_concurrent_requests(tracer, iast_span_defaults): # Ensures quota is always within bounds after multithreading scenario assert 0 <= oc._request_quota <= MAX_REQUESTS + + +@pytest.mark.skip_iast_check_logs +def test_oce_concurrent_requests_futures_in_spans(tracer, iast_span_defaults, caplog): + import concurrent.futures + + results = [] + num_requests = 5 + with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG), concurrent.futures.ThreadPoolExecutor( + max_workers=5 + ) as executor: + futures = [] + for _ in range(0, num_requests): + futures.append(executor.submit(function_with_vulnerabilities_1, tracer)) + futures.append(executor.submit(function_with_vulnerabilities_2, tracer)) + futures.append(executor.submit(function_with_vulnerabilities_3, tracer)) + + for future in concurrent.futures.as_completed(futures): + results.append(future.result()) + + span_report = get_iast_reporter() + + assert len(span_report.vulnerabilities) + + assert len(results) == num_requests * 3 + assert len(span_report.vulnerabilities) >= 1 diff --git a/tests/appsec/iast/test_processor.py b/tests/appsec/iast/test_processor.py index bba44ae5c01..877da0f7830 100644 --- a/tests/appsec/iast/test_processor.py +++ b/tests/appsec/iast/test_processor.py @@ -3,14 +3,14 @@ import pytest from ddtrace.appsec._constants import IAST -from ddtrace.appsec._iast._patch_modules import patch_iast +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import get_iast_reporter +from ddtrace.constants import AUTO_KEEP from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.ext import SpanTypes -from ddtrace.internal import core from tests.utils import DummyTracer from tests.utils import override_env -from tests.utils import override_global_config def traced_function(tracer): @@ -27,42 +27,62 @@ def traced_function(tracer): @pytest.mark.skip_iast_check_logs -def test_appsec_iast_processor(): +def test_appsec_iast_processor(iast_context_defaults): """ test_appsec_iast_processor. This test throws 'finished span not connected to a trace' log error """ - with override_global_config(dict(_iast_enabled=True)): - patch_iast() + tracer = DummyTracer(iast_enabled=True) + span = traced_function(tracer) + tracer._on_span_finish(span) + + span_report = get_iast_reporter() + result = span.get_tag(IAST.JSON) + + assert len(json.loads(result)["vulnerabilities"]) == 1 + assert len(span_report.vulnerabilities) == 1 + + +@pytest.mark.parametrize("sampling_rate", ["0.0", "0.5", "1.0"]) +def test_appsec_iast_processor_ensure_span_is_manual_keep(iast_context_defaults, sampling_rate): + """ + test_appsec_iast_processor_ensure_span_is_manual_keep. + This test throws 'finished span not connected to a trace' log error + """ + with override_env(dict(DD_TRACE_SAMPLE_RATE=sampling_rate)): + oce.reconfigure() tracer = DummyTracer(iast_enabled=True) span = traced_function(tracer) tracer._on_span_finish(span) - span_report = core.get_item(IAST.CONTEXT_KEY, span=span) result = span.get_tag(IAST.JSON) - assert len(span_report.vulnerabilities) == 1 assert len(json.loads(result)["vulnerabilities"]) == 1 + assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP @pytest.mark.skip_iast_check_logs -@pytest.mark.parametrize("sampling_rate", ["0.0", "0.5", "1.0"]) -def test_appsec_iast_processor_ensure_span_is_manual_keep(sampling_rate): +@pytest.mark.parametrize("sampling_rate", ["0.0", "100"]) +def test_appsec_iast_processor_ensure_span_is_sampled(iast_context_defaults, sampling_rate): """ test_appsec_iast_processor_ensure_span_is_manual_keep. This test throws 'finished span not connected to a trace' log error """ - with override_env(dict(DD_TRACE_SAMPLE_RATE=sampling_rate)), override_global_config(dict(_iast_enabled=True)): - patch_iast() - + with override_env(dict(DD_IAST_REQUEST_SAMPLING=sampling_rate)): + oce.reconfigure() tracer = DummyTracer(iast_enabled=True) span = traced_function(tracer) tracer._on_span_finish(span) result = span.get_tag(IAST.JSON) - - assert len(json.loads(result)["vulnerabilities"]) == 1 - assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + if sampling_rate == "0.0": + assert result is None + assert span.get_metric(SAMPLING_PRIORITY_KEY) is AUTO_KEEP + assert span.get_metric(IAST.ENABLED) == 0.0 + else: + assert len(json.loads(result)["vulnerabilities"]) == 1 + assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert span.get_metric(IAST.ENABLED) == 1.0 diff --git a/tests/appsec/iast/test_taint_utils.py b/tests/appsec/iast/test_taint_utils.py index a60cc2c547a..6749c2788ec 100644 --- a/tests/appsec/iast/test_taint_utils.py +++ b/tests/appsec/iast/test_taint_utils.py @@ -1,10 +1,7 @@ import mock import pytest -from ddtrace.appsec._iast import oce -from ddtrace.appsec._iast._patch_modules import patch_iast from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_utils import LazyTaintDict @@ -12,13 +9,20 @@ from ddtrace.appsec._iast._taint_utils import check_tainted_dbapi_args -def setup(): - patch_iast() - create_context() - oce._enabled = True +@pytest.fixture +def lazy_taint_json_patch(): + from ddtrace.appsec._iast._patches.json_tainting import patched_json_encoder_default + from ddtrace.appsec._iast._patches.json_tainting import try_unwrap + from ddtrace.appsec._iast._patches.json_tainting import try_wrap_function_wrapper + + try_wrap_function_wrapper("json.encoder", "JSONEncoder.default", patched_json_encoder_default) + try_wrap_function_wrapper("simplejson.encoder", "JSONEncoder.default", patched_json_encoder_default) + yield + try_unwrap("json.encoder", "JSONEncoder.default") + try_unwrap("simplejson.encoder", "JSONEncoder.default") -def test_tainted_types(): +def test_tainted_types(iast_context_defaults): tainted = taint_pyobject( pyobject="hello", source_name="request_body", source_value="hello", source_origin=OriginType.PARAMETER ) @@ -68,7 +72,7 @@ def test_tainted_types(): assert not is_pyobject_tainted(not_tainted) -def test_tainted_getitem(): +def test_tainted_getitem(iast_context_defaults): knights = {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", "")), "not string": 1} tainted_knights = LazyTaintDict( {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", "")), "not string": 1}, @@ -89,7 +93,7 @@ def test_tainted_getitem(): tainted_knights["arthur"] -def test_tainted_get(): +def test_tainted_get(iast_context_defaults): knights = {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", "")), "not string": 1} tainted_knights = LazyTaintDict( {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", "")), "not string": 1}, @@ -119,7 +123,7 @@ def test_tainted_get(): assert not is_pyobject_tainted(robin) -def test_tainted_items(): +def test_tainted_items(iast_context_defaults): knights = {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", ""))} tainted_knights = LazyTaintDict( {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", ""))}, @@ -137,7 +141,7 @@ def test_tainted_items(): assert not is_pyobject_tainted(v) -def test_tainted_keys_and_values(): +def test_tainted_keys_and_values(iast_context_defaults): knights = {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", ""))} tainted_knights = LazyTaintDict( {"gallahad": "".join(("the pure", "")), "robin": "".join(("the brave", ""))}, @@ -157,7 +161,7 @@ def test_tainted_keys_and_values(): assert not is_pyobject_tainted(v) -def test_recursivity(): +def test_recursivity(iast_context_defaults): tainted_dict = LazyTaintDict( { "tr_key_001": ["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}], @@ -180,7 +184,7 @@ def check_taint(v): check_taint(tainted_dict) -def test_checked_tainted_args(): +def test_checked_tainted_args(iast_context_defaults): cursor = mock.Mock() cursor.execute.__name__ = "execute" cursor.executemany.__name__ = "executemany" @@ -235,21 +239,8 @@ def test_checked_tainted_args(): ) -@pytest.fixture -def lazy_taint_json_patch(): - from ddtrace.appsec._iast._patches.json_tainting import patched_json_encoder_default - from ddtrace.appsec._iast._patches.json_tainting import try_unwrap - from ddtrace.appsec._iast._patches.json_tainting import try_wrap_function_wrapper - - try_wrap_function_wrapper("json.encoder", "JSONEncoder.default", patched_json_encoder_default) - try_wrap_function_wrapper("simplejson.encoder", "JSONEncoder.default", patched_json_encoder_default) - yield - try_unwrap("json.encoder", "JSONEncoder.default") - try_unwrap("simplejson.encoder", "JSONEncoder.default") - - @pytest.mark.usefixtures("lazy_taint_json_patch") -def test_json_encode_dict(): +def test_json_encode_dict(iast_context_defaults): import json tainted_dict = LazyTaintDict( @@ -267,7 +258,7 @@ def test_json_encode_dict(): @pytest.mark.usefixtures("lazy_taint_json_patch") -def test_json_encode_list(): +def test_json_encode_list(iast_context_defaults): import json tainted_list = LazyTaintList( @@ -279,7 +270,7 @@ def test_json_encode_list(): @pytest.mark.usefixtures("lazy_taint_json_patch") -def test_simplejson_encode_dict(): +def test_simplejson_encode_dict(iast_context_defaults): import simplejson as json tainted_dict = LazyTaintDict( @@ -297,7 +288,7 @@ def test_simplejson_encode_dict(): @pytest.mark.usefixtures("lazy_taint_json_patch") -def test_simplejson_encode_list(): +def test_simplejson_encode_list(iast_context_defaults): import simplejson as json tainted_list = LazyTaintList( @@ -308,7 +299,7 @@ def test_simplejson_encode_list(): assert json.dumps(tainted_list) == '["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}]' -def test_taint_structure(): +def test_taint_structure(iast_context_defaults): from ddtrace.appsec._iast._taint_utils import taint_structure d = {1: "foo"} diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 42470e61d5b..5ebe1b36fc4 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -65,7 +65,8 @@ def test_metric_verbosity(lvl, env_lvl, expected_result): assert metric_verbosity(lvl)(lambda: 1)() == expected_result -def test_metric_executed_sink(no_request_sampling, telemetry_writer): +@pytest.mark.skip_iast_check_logs +def test_metric_executed_sink(no_request_sampling, telemetry_writer, caplog): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) ): @@ -87,12 +88,12 @@ def test_metric_executed_sink(no_request_sampling, telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST].values() - assert len(generate_metrics) >= 1 + assert len(generate_metrics) == 1 # Remove potential sinks from internal usage of the lib (like http.client, used to communicate with # the agent) filtered_metrics = [metric for metric in generate_metrics if metric._tags[0] == ("vulnerability_type", "WEAK_HASH")] assert [metric._tags for metric in filtered_metrics] == [(("vulnerability_type", "WEAK_HASH"),)] - assert span.get_metric("_dd.iast.telemetry.executed.sink.weak_hash") > 0 + assert span.get_metric("_dd.iast.telemetry.executed.sink.weak_hash") == 2 # request.tainted metric is None because AST is not running in this test assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) is None diff --git a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py index c64ca7504d2..e919c9704d7 100644 --- a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py +++ b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py @@ -1,11 +1,13 @@ import pytest from tests.utils import override_env +from tests.utils import override_global_config @pytest.mark.asyncio async def test_aggregated_leaks(): - with override_env({"DD_IAST_ENABLED": "true"}): + env = {"DD_IAST_ENABLED": "true", "DD_IAST_REQUEST_SAMPLING": "100"} + with override_env(env), override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): from scripts.iast.leak_functions import iast_leaks result = await iast_leaks(75000, 1.0, 100) == 0 diff --git a/tests/appsec/iast_memcheck/conftest.py b/tests/appsec/iast_memcheck/conftest.py index cc5a7e42ffa..089509d7476 100644 --- a/tests/appsec/iast_memcheck/conftest.py +++ b/tests/appsec/iast_memcheck/conftest.py @@ -1,9 +1,9 @@ import pytest -from tests.appsec.iast.conftest import iast_span +from tests.appsec.iast.conftest import iast_context @pytest.fixture -def iast_span_defaults(tracer): - for t in iast_span(tracer, dict(DD_IAST_ENABLED="true")): +def iast_context_defaults(tracer): + for t in iast_context(dict(DD_IAST_ENABLED="true")): yield t diff --git a/tests/appsec/iast_memcheck/test_iast_mem_check.py b/tests/appsec/iast_memcheck/test_iast_mem_check.py index c8138eaa88c..b8c5c313143 100644 --- a/tests/appsec/iast_memcheck/test_iast_mem_check.py +++ b/tests/appsec/iast_memcheck/test_iast_mem_check.py @@ -4,24 +4,19 @@ from pytest_memray import LeaksFilterFunction from pytest_memray import Stack -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._stacktrace import get_info_frame -from ddtrace.internal import core +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size +from ddtrace.appsec._iast._taint_tracking import create_context +from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import initializer_size +from ddtrace.appsec._iast._taint_tracking import num_objects_tainted +from ddtrace.appsec._iast._taint_tracking import reset_context +from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module +from tests.appsec.iast.taint_sinks.conftest import _get_span_report from tests.appsec.iast_memcheck._stacktrace_py import get_info_frame as get_info_frame_py from tests.appsec.iast_memcheck.fixtures.stacktrace import func_1 -from tests.utils import override_env - - -with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import initializer_size - from ddtrace.appsec._iast._taint_tracking import num_objects_tainted - from ddtrace.appsec._iast._taint_tracking import reset_context - from ddtrace.appsec._iast._taint_tracking import taint_pyobject FIXTURES_PATH = "tests/appsec/iast/fixtures/propagation_path.py" @@ -65,7 +60,7 @@ def __call__(self, stack: Stack) -> bool: (b"taintsource1", bytearray(b"taintsource2")), ], ) -def test_propagation_memory_check(origin1, origin2, iast_span_defaults): +def test_propagation_memory_check(origin1, origin2, iast_context_defaults): """Biggest allocating functions: - join_aspect: ddtrace/appsec/_iast/_taint_tracking/aspects.py:124 -> 8.0KiB - _prepare_report: ddtrace/appsec/_iast/taint_sinks/_base.py:111 -> 8.0KiB @@ -85,7 +80,7 @@ def test_propagation_memory_check(origin1, origin2, iast_span_defaults): ) result = mod.propagation_memory_check(tainted_string_1, tainted_string_2) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert len(span_report.sources) > 0 assert len(span_report.vulnerabilities) > 0 assert len(get_tainted_ranges(result)) == 6 @@ -126,7 +121,7 @@ def test_propagation_memory_check(origin1, origin2, iast_span_defaults): (b"taintsource1", bytearray(b"taintsource2")), ], ) -async def test_propagation_memory_check_async(origin1, origin2, iast_span_defaults): +async def test_propagation_memory_check_async(origin1, origin2, iast_context_defaults): """Biggest allocating functions: - join_aspect: ddtrace/appsec/_iast/_taint_tracking/aspects.py:124 -> 8.0KiB - _prepare_report: ddtrace/appsec/_iast/taint_sinks/_base.py:111 -> 8.0KiB @@ -146,7 +141,7 @@ async def test_propagation_memory_check_async(origin1, origin2, iast_span_defaul ) result = await mod.propagation_memory_check_async(tainted_string_1, tainted_string_2) - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert len(span_report.sources) > 0 assert len(span_report.vulnerabilities) > 0 assert len(get_tainted_ranges(result)) == 6 diff --git a/tests/appsec/iast_tdd_propagation/flask_orm_app.py b/tests/appsec/iast_tdd_propagation/flask_orm_app.py index 670228f1880..b7fcf9f59c2 100644 --- a/tests/appsec/iast_tdd_propagation/flask_orm_app.py +++ b/tests/appsec/iast_tdd_propagation/flask_orm_app.py @@ -12,9 +12,8 @@ from flask import request from ddtrace import tracer -from ddtrace.appsec._constants import IAST from ddtrace.appsec.iast import ddtrace_iast_flask_patch -from ddtrace.internal import core +from tests.appsec.iast.taint_sinks.conftest import _get_span_report from tests.utils import override_env @@ -60,17 +59,17 @@ def shutdown(): def tainted_view(): param = request.args.get("param", "param") - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) + report = _get_span_report() - assert not (report and report[0]) + assert not (report and report) orm_impl.execute_query("select * from User where name = '" + param + "'") response = ResultResponse(param) - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) - if report and report[0]: - response.sources = report[0].sources[0].value - response.vulnerabilities = list(report[0].vulnerabilities)[0].type + report = _get_span_report() + if report: + response.sources = report.sources[0].value + response.vulnerabilities = list(report.vulnerabilities)[0].type return response.json() @@ -79,17 +78,17 @@ def tainted_view(): def untainted_view(): param = request.args.get("param", "param") - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) + report = _get_span_report() - assert not (report and report[0]) + assert not (report and report) orm_impl.execute_untainted_query("select * from User where name = '" + param + "'") response = ResultResponse(param) - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) - if report and report[0]: - response.sources = report[0].sources[0].value - response.vulnerabilities = list(report[0].vulnerabilities)[0].type + report = _get_span_report() + if report: + response.sources = report.sources[0].value + response.vulnerabilities = list(report.vulnerabilities)[0].type return response.json() diff --git a/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py b/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py index 905ef0c7253..56074989bc5 100644 --- a/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py +++ b/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py @@ -6,9 +6,8 @@ from flask import request from ddtrace import tracer -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.internal import core +from tests.appsec.iast.taint_sinks.conftest import _get_span_report class ResultResponse: @@ -46,10 +45,10 @@ def secure_weak_cipher(): crypt_obj.encrypt(data) response = ResultResponse(param) - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) - if report and report[0]: - response.sources = report[0].sources[0].value - response.vulnerabilities = list(report[0].vulnerabilities)[0].type + report = _get_span_report() + if report: + response.sources = report.sources[0].value + response.vulnerabilities = list(report.vulnerabilities)[0].type return response.json() @@ -63,10 +62,10 @@ def insecure_weak_cipher(): crypt_obj.encrypt(data) response = ResultResponse(param) - report = core.get_items([IAST.CONTEXT_KEY], tracer.current_root_span()) - if report and report[0]: - response.sources = report[0].sources[0].value if report[0].sources else "" - response.vulnerabilities = list(report[0].vulnerabilities)[0].type + report = _get_span_report() + if report: + response.sources = report.sources[0].value if report.sources else "" + response.vulnerabilities = list(report.vulnerabilities)[0].type return response.json() diff --git a/tests/appsec/integrations/pygoat_tests/test_pygoat.py b/tests/appsec/integrations/pygoat_tests/test_pygoat.py index df180e0b9fb..13e8eb9d23f 100644 --- a/tests/appsec/integrations/pygoat_tests/test_pygoat.py +++ b/tests/appsec/integrations/pygoat_tests/test_pygoat.py @@ -4,11 +4,11 @@ import pytest import requests -from tests.appsec.iast.conftest import iast_span_defaults +from tests.appsec.iast.conftest import iast_context_defaults from tests.utils import flaky -span_defaults = iast_span_defaults # So ruff does not remove it +span_defaults = iast_context_defaults # So ruff does not remove it # Note: these tests require the testagent and pygoat images to be up from the docker-compose file @@ -138,7 +138,7 @@ def test_sqli(client): @pytest.mark.skip("TODO: SSRF is not implemented for open()") -def test_ssrf1(client, tracer, iast_span_defaults): +def test_ssrf1(client, iast_context_defaults): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject @@ -155,7 +155,7 @@ def test_ssrf1(client, tracer, iast_span_defaults): assert vulnerability_in_traces("SSRF", client.agent_session) -def test_ssrf2(client, tracer, span_defaults): +def test_ssrf2(client, iast_context_defaults): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject diff --git a/tests/appsec/integrations/test_flask_iast_patching.py b/tests/appsec/integrations/test_flask_iast_patching.py index e0847831260..5dd1baab67c 100644 --- a/tests/appsec/integrations/test_flask_iast_patching.py +++ b/tests/appsec/integrations/test_flask_iast_patching.py @@ -4,6 +4,7 @@ from tests.appsec.appsec_utils import gunicorn_server from tests.appsec.integrations.utils import _PORT from tests.appsec.integrations.utils import _request_200 +from tests.utils import flaky def test_flask_iast_ast_patching_import_error(): @@ -27,6 +28,7 @@ def test_flask_iast_ast_patching_import_error(): assert response.content == b"False" +@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.parametrize("style", ["re_module", "re_object"]) @pytest.mark.parametrize("endpoint", ["re", "non-re"]) @pytest.mark.parametrize( diff --git a/tests/appsec/integrations/test_langchain.py b/tests/appsec/integrations/test_langchain.py index 325bfe670d5..795d48db8b9 100644 --- a/tests/appsec/integrations/test_langchain.py +++ b/tests/appsec/integrations/test_langchain.py @@ -1,12 +1,11 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.constants import VULN_CMDI -from ddtrace.internal import core from ddtrace.internal.module import is_module_installed from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.appsec.iast.conftest import iast_span_defaults # noqa: F401 +from tests.appsec.iast.conftest import iast_context_defaults # noqa: F401 from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.conftest import _get_span_report from tests.utils import override_env @@ -19,7 +18,7 @@ @pytest.mark.skipif(not is_module_installed("langchain"), reason="Langchain tests work on 3.9 or higher") -def test_openai_llm_appsec_iast_cmdi(iast_span_defaults): # noqa: F811 +def test_openai_llm_appsec_iast_cmdi(iast_context_defaults): # noqa: F811 mod = _iast_patched_module(FIXTURES_MODULE) string_to_taint = "I need to use the terminal tool to print a Hello World" prompt = taint_pyobject( @@ -31,7 +30,7 @@ def test_openai_llm_appsec_iast_cmdi(iast_span_defaults): # noqa: F811 res = mod.patch_langchain(prompt) assert res == "4" - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + span_report = _get_span_report() assert span_report data = span_report.build_and_scrub_value_parts() vulnerability = data["vulnerabilities"][0] diff --git a/tests/appsec/integrations/test_psycopg2.py b/tests/appsec/integrations/test_psycopg2.py index 956d3e3ef56..d1998eff6ca 100644 --- a/tests/appsec/integrations/test_psycopg2.py +++ b/tests/appsec/integrations/test_psycopg2.py @@ -1,20 +1,22 @@ import psycopg2.extensions as ext +import pytest -from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_utils import LazyTaintList +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.utils import override_env from tests.utils import override_global_config -with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted - - -def setup_module(): - create_context() - oce._enabled = True +@pytest.fixture(autouse=True) +def iast_create_context(): + env = {"DD_IAST_REQUEST_SAMPLING": "100"} + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + _start_iast_context_and_oce() + yield + _end_iast_context_and_oce() def test_list(): diff --git a/tests/contrib/dbapi/test_dbapi_appsec.py b/tests/contrib/dbapi/test_dbapi_appsec.py index 27e615d93d0..81e8971f271 100644 --- a/tests/contrib/dbapi/test_dbapi_appsec.py +++ b/tests/contrib/dbapi/test_dbapi_appsec.py @@ -7,33 +7,50 @@ from ddtrace.contrib.dbapi import TracedCursor from ddtrace.settings import Config from ddtrace.settings.integration import IntegrationConfig +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.utils import TracerTestCase from tests.utils import override_env +from tests.utils import override_global_config + + +IAST_ENV = {"DD_IAST_ENABLED": "True", "DD_IAST_REQUEST_SAMPLING": "100"} class TestTracedCursor(TracerTestCase): def setUp(self): super(TestTracedCursor, self).setUp() - from ddtrace.appsec._iast._taint_tracking import create_context - - create_context() + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ), override_env(IAST_ENV): + _start_iast_context_and_oce() self.cursor = mock.Mock() self.cursor.execute.__name__ = "execute" def tearDown(self): - from ddtrace.appsec._iast._taint_tracking import reset_context - - reset_context() + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ), override_env(IAST_ENV): + _end_iast_context_and_oce() @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query(self): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import taint_pyobject - with mock.patch("ddtrace.contrib.dbapi._is_iast_enabled", return_value=True), mock.patch( + with override_global_config( + dict( + _iast_enabled=True, + ) + ), mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" ) as mock_sql_injection_report: - oce._enabled = True query = "SELECT * FROM db;" query = taint_pyobject(query, source_name="query", source_value=query, source_origin=OriginType.PARAMETER) @@ -47,11 +64,10 @@ def test_tainted_query(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query_args(self): - with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject - with mock.patch("ddtrace.contrib.dbapi._is_iast_enabled", return_value=True), mock.patch( + with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" ) as mock_sql_injection_report: oce._enabled = True @@ -71,9 +87,7 @@ def test_tainted_query_args(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_untainted_query(self): - with override_env({"DD_IAST_ENABLED": "True"}), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), mock.patch( + with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" ) as mock_sql_injection_report: query = "SELECT * FROM db;" @@ -88,9 +102,7 @@ def test_untainted_query(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_untainted_query_and_args(self): - with override_env({"DD_IAST_ENABLED": "True"}), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), mock.patch( + with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" ) as mock_sql_injection_report: query = "SELECT ? FROM db;" @@ -106,11 +118,10 @@ def test_untainted_query_and_args(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query_iast_disabled(self): - with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject - with mock.patch("ddtrace.contrib.dbapi._is_iast_enabled", return_value=False), mock.patch( + with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" ) as mock_sql_injection_report: oce._enabled = True diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index 76805886927..31c69c3fc11 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -24,14 +24,11 @@ @pytest.fixture(autouse=True) -def reset_context(): - with override_env({IAST.ENV: "True"}): - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import reset_context - - _ = create_context() +def iast_context(): + with override_env( + {IAST.ENV: "True", "DD_IAST_REQUEST_SAMPLING": "100", "_DD_APPSEC_DEDUPLICATION_ENABLED": "false"} + ): yield - reset_context() # The log contains "[IAST]" but "[IAST] create_context" or "[IAST] reset_context" are valid diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index f8db3ffc051..d9c097d9ac8 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -7,6 +7,7 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import _iast_start_request from ddtrace.appsec._iast._patches.json_tainting import patch as patch_json from ddtrace.appsec._iast._utils import _is_python_version_supported as python_supported_by_iast from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION @@ -16,6 +17,8 @@ from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks.header_injection import patch as patch_header_injection from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli +from tests.appsec.iast.conftest import _end_iast_context_and_oce +from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.appsec.iast.iast_utils import get_line_and_hash from tests.contrib.flask import BaseFlaskTestCase from tests.utils import override_env @@ -30,17 +33,6 @@ flask_version = tuple([int(v) for v in version("flask").split(".")]) -@pytest.fixture(autouse=True) -def reset_context(): - with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import reset_context - - yield - reset_context() - _ = create_context() - - class FlaskAppSecIASTEnabledTestCase(BaseFlaskTestCase): @pytest.fixture(autouse=True) def inject_fixtures(self, caplog): @@ -58,11 +50,22 @@ def setUp(self): patch_header_injection() patch_json() oce.reconfigure() + _start_iast_context_and_oce() self.tracer._iast_enabled = True self.tracer._asm_enabled = True self.tracer.configure(api_version="v0.4") + def tearDown(self): + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ), override_env(IAST_ENV): + _end_iast_context_and_oce() + super(FlaskAppSecIASTEnabledTestCase, self).tearDown() + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_flask_full_sqli_iast_http_request_path_parameter(self): @self.app.route("/sqli//", methods=["GET", "POST"]) @@ -343,6 +346,10 @@ def sqli_6(param_str): return request.query_string, 200 + class MockSpan: + _trace_id_64bits = 17577308072598193742 + + _end_iast_context_and_oce() with override_global_config( dict( _iast_enabled=True, @@ -350,11 +357,12 @@ def sqli_6(param_str): ) ), override_env(IAST_ENV_SAMPLING_0): oce.reconfigure() - + _iast_start_request(MockSpan()) resp = self.client.post("/sqli/hello/?select%20from%20table", data={"name": "test"}) assert resp.status_code == 200 root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 0.0 @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index 19e3b6d2cd2..1fefc505d7f 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -18,7 +18,6 @@ from ddtrace import config from ddtrace._trace.context import Context from ddtrace._trace.span import Span -from ddtrace.appsec._constants import IAST from ddtrace.contrib import trace_utils from ddtrace.contrib.trace_utils import _get_request_header_client_ip from ddtrace.ext import SpanTypes @@ -541,14 +540,6 @@ def test_set_http_meta_no_headers(mock_store_headers, span, int_config): mock_store_headers.assert_not_called() -def test_set_http_meta_insecure_cookies_iast_disabled(span, int_config): - with override_global_config(dict(_iast_enabled=False)): - cookies = {"foo": "bar"} - trace_utils.set_http_meta(span, int_config.myint, request_cookies=cookies) - span_report = core.get_item(IAST.CONTEXT_KEY, span=span) - assert not span_report - - @mock.patch("ddtrace.contrib.trace_utils._store_headers") @pytest.mark.parametrize( "user_agent_key,user_agent_value,expected_keys,expected", From d983d426392a04d50691871278636e9aed9122ab Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Wed, 16 Oct 2024 14:12:18 -0400 Subject: [PATCH 005/372] feat(tracer): [SVLS-5675] DynamoDB BatchWriteItem pointers (#11039) These are a mix of `PutItem` and `DeleteItem` logic, so they're getting pretty funky. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Zachary Groves <32471391+ZStriker19@users.noreply.github.com> --- .../utils_botocore/span_pointers/__init__.py | 2 +- .../utils_botocore/span_pointers/dynamodb.py | 137 ++++++ ...amodb-batchwriteitem-7c2321a26f212520.yaml | 4 + .../utils_botocore/test_span_pointers.py | 444 +++++++++++++++++- 4 files changed, 582 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/span-pointers-aws-dynamodb-batchwriteitem-7c2321a26f212520.yaml diff --git a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py index ca3d0e43b74..8f6aab93445 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py @@ -22,7 +22,7 @@ def extract_span_pointers_from_successful_botocore_response( if endpoint_name == "dynamodb": return _extract_span_pointers_for_dynamodb_response( - dynamodb_primary_key_names_for_tables, operation_name, request_parameters + dynamodb_primary_key_names_for_tables, operation_name, request_parameters, response ) return [] diff --git a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py index 9456dc04756..a1b13e62fc3 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py @@ -1,7 +1,12 @@ +from copy import deepcopy +import itertools +import sys from typing import Any from typing import Dict from typing import List from typing import Set +from typing import Union +from typing import cast from ddtrace._trace._span_pointer import _SpanPointerDescription from ddtrace._trace._span_pointer import _SpanPointerDirection @@ -9,6 +14,11 @@ from ddtrace.internal.logger import get_logger +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict + log = get_logger(__name__) @@ -23,10 +33,30 @@ _DynamoDBItemPrimaryKey = Dict[_DynamoDBItemFieldName, _DynamoDBItemPrimaryKeyValue] +class _DynamoDBPutRequest(TypedDict): + Item: _DynamoDBItem + + +class _DynamoDBPutRequestWriteRequest(TypedDict): + PutRequest: _DynamoDBPutRequest + + +class _DynamoDBDeleteRequest(TypedDict): + Key: _DynamoDBItemPrimaryKey + + +class _DynamoDBDeleteRequestWriteRequest(TypedDict): + DeleteRequest: _DynamoDBDeleteRequest + + +_DynamoDBWriteRequest = Union[_DynamoDBPutRequestWriteRequest, _DynamoDBDeleteRequestWriteRequest] + + def _extract_span_pointers_for_dynamodb_response( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], operation_name: str, request_parameters: Dict[str, Any], + response: Dict[str, Any], ) -> List[_SpanPointerDescription]: if operation_name == "PutItem": return _extract_span_pointers_for_dynamodb_putitem_response( @@ -39,6 +69,13 @@ def _extract_span_pointers_for_dynamodb_response( request_parameters, ) + if operation_name == "BatchWriteItem": + return _extract_span_pointers_for_dynamodb_batchwriteitem_response( + dynamodb_primary_key_names_for_tables, + request_parameters, + response, + ) + return [] @@ -93,6 +130,73 @@ def _extract_span_pointers_for_dynamodb_keyed_operation_response( return [] +def _extract_span_pointers_for_dynamodb_batchwriteitem_response( + dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], + request_parameters: Dict[str, Any], + response: Dict[str, Any], +) -> List[_SpanPointerDescription]: + try: + requested_items = request_parameters["RequestItems"] + unprocessed_items = response.get("UnprocessedItems", {}) + + processed_items = _identify_dynamodb_batch_write_item_processed_items(requested_items, unprocessed_items) + + return list( + itertools.chain.from_iterable( + [ + _aws_dynamodb_item_span_pointer_description( + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=_aws_dynamodb_item_primary_key_from_write_request( + dynamodb_primary_key_names_for_tables, table_name, write_request + ), + ) + for write_request in processed_items_for_table + ] + for table_name, processed_items_for_table in processed_items.items() + ) + ) + + except Exception as e: + log.warning( + "failed to generate DynamoDB.BatchWriteItem span pointer: %s", + str(e), + ) + return [] + + +def _identify_dynamodb_batch_write_item_processed_items( + requested_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], + unprocessed_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], +) -> Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]]: + processed_items = {} + + if not all(table_name in requested_items for table_name in unprocessed_items): + raise ValueError("unprocessed items include tables not in the requested items") + + for table_name, requested_write_requests in requested_items.items(): + if table_name not in unprocessed_items: + processed_items[table_name] = deepcopy(requested_write_requests) + + else: + if not all( + unprocessed_write_request in requested_write_requests + for unprocessed_write_request in unprocessed_items[table_name] + ): + raise ValueError("unprocessed write requests include items not in the requested write requests") + + these_processed_items = [ + deepcopy(processed_write_request) + for processed_write_request in requested_write_requests + if processed_write_request not in unprocessed_items[table_name] + ] + if these_processed_items: + # no need to include them if they are all unprocessed + processed_items[table_name] = these_processed_items + + return processed_items + + def _aws_dynamodb_item_primary_key_from_item( primary_key_field_names: Set[_DynamoDBItemFieldName], item: _DynamoDBItem, @@ -108,6 +212,39 @@ def _aws_dynamodb_item_primary_key_from_item( } +def _aws_dynamodb_item_primary_key_from_write_request( + dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], + table_name: _DynamoDBTableName, + write_request: _DynamoDBWriteRequest, +) -> _DynamoDBItemPrimaryKey: + # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_WriteRequest.html + + if len(write_request) != 1: + raise ValueError(f"unexpected number of write request fields: {len(write_request)}") + + if "PutRequest" in write_request: + # Unfortunately mypy doesn't properly see the if statement above as a + # type-narrowing from _DynamoDBWriteRequest to + # _DynamoDBPutRequestWriteRequest, so we help it out ourselves. + write_request = cast(_DynamoDBPutRequestWriteRequest, write_request) + + return _aws_dynamodb_item_primary_key_from_item( + dynamodb_primary_key_names_for_tables[table_name], + write_request["PutRequest"]["Item"], + ) + + elif "DeleteRequest" in write_request: + # Unfortunately mypy doesn't properly see the if statement above as a + # type-narrowing from _DynamoDBWriteRequest to + # _DynamoDBDeleteRequestWriteRequest, so we help it out ourselves. + write_request = cast(_DynamoDBDeleteRequestWriteRequest, write_request) + + return write_request["DeleteRequest"]["Key"] + + else: + raise ValueError(f"unexpected write request structure: {''.join(sorted(write_request.keys()))}") + + def _aws_dynamodb_item_span_pointer_description( pointer_direction: _SpanPointerDirection, table_name: _DynamoDBTableName, diff --git a/releasenotes/notes/span-pointers-aws-dynamodb-batchwriteitem-7c2321a26f212520.yaml b/releasenotes/notes/span-pointers-aws-dynamodb-batchwriteitem-7c2321a26f212520.yaml new file mode 100644 index 00000000000..efb0921a052 --- /dev/null +++ b/releasenotes/notes/span-pointers-aws-dynamodb-batchwriteitem-7c2321a26f212520.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + botocore: Adds span pointers for successful DynamoDB ``BatchWriteItem`` spans. Table Primary Keys will need to be provided with the ``ddtrace.config.botocore.dynamodb_primary_key_names_for_tables`` option or the ``DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS`` environment variable to correctly handle the ``PutRequest`` items. diff --git a/tests/tracer/utils_botocore/test_span_pointers.py b/tests/tracer/utils_botocore/test_span_pointers.py index 11f846771bf..1e5cd69170f 100644 --- a/tests/tracer/utils_botocore/test_span_pointers.py +++ b/tests/tracer/utils_botocore/test_span_pointers.py @@ -4,6 +4,7 @@ from typing import List from typing import NamedTuple from typing import Optional +from typing import Set import mock import pytest @@ -11,7 +12,11 @@ from ddtrace._trace._span_pointer import _SpanPointerDescription from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace.utils_botocore.span_pointers import extract_span_pointers_from_successful_botocore_response +from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _aws_dynamodb_item_primary_key_from_write_request from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _aws_dynamodb_item_span_pointer_hash +from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _DynamoDBTableName +from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _DynamoDBWriteRequest +from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _identify_dynamodb_batch_write_item_processed_items from ddtrace._trace.utils_botocore.span_pointers.s3 import _aws_s3_object_span_pointer_hash @@ -616,6 +621,105 @@ class PointersCase(NamedTuple): expected_pointers=[], expected_warning_regex=".*'Key'.*", ), + PointersCase( + name="BatchWriteItem works with multiple items and tables", + endpoint_name="dynamodb", + operation_name="BatchWriteItem", + request_parameters={ + "RequestItems": { + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + { + "PutRequest": { + "Item": { + "some-key": {"S": "will-not-complete"}, + }, + }, + }, + ], + "unknown-table": [ + { + "DeleteRequest": { + "Key": { + "some-key": {"S": "some-value"}, + }, + }, + }, + { + "PutRequest": { + "Item": { + "some-key": {"S": "will-also-not-complete"}, + }, + }, + }, + ], + }, + }, + response={ + "UnprocessedItems": { + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "will-not-complete"}, + }, + }, + }, + ], + "unknown-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "will-also-not-complete"}, + }, + }, + }, + ], + }, + }, + expected_pointers=[ + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="d8840182e4052ee105348b033e0a6810", + extra_attributes={}, + ), + ], + expected_warning_regex=None, + ), + PointersCase( + name="BatchWriteItem still needs the mapping sometimes", + endpoint_name="dynamodb", + operation_name="BatchWriteItem", + request_parameters={ + "RequestItems": { + "unknown-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + }, + response={}, + expected_pointers=[], + expected_warning_regex=".*unknown-table.*", + ), ], ids=lambda case: case.name, ) @@ -624,7 +728,7 @@ def test_pointers(self, pointers_case: PointersCase) -> None: # behavior, so we have to go a bit deeper. with mock.patch.object(logging.Logger, "warning") as mock_logger: - assert ( + assert sorted( extract_span_pointers_from_successful_botocore_response( dynamodb_primary_key_names_for_tables={ "some-table": {"some-key"}, @@ -633,9 +737,9 @@ def test_pointers(self, pointers_case: PointersCase) -> None: operation_name=pointers_case.operation_name, request_parameters=pointers_case.request_parameters, response=pointers_case.response, - ) - == pointers_case.expected_pointers - ) + ), + key=lambda pointer: pointer.pointer_hash, + ) == sorted(pointers_case.expected_pointers, key=lambda pointer: pointer.pointer_hash) if pointers_case.expected_warning_regex is None: mock_logger.assert_not_called() @@ -650,3 +754,335 @@ def test_pointers(self, pointers_case: PointersCase) -> None: pointers_case.expected_warning_regex, fmt % tuple(other_args), ) + + +class TestDynamoDBWriteRequestLogic: + class WriteRequestPrimaryKeyCase(NamedTuple): + name: str + table_name: str + write_request: _DynamoDBWriteRequest + primary_key: Optional[Dict[str, Dict[str, str]]] + expected_exception_regex: Optional[str] + + @pytest.mark.parametrize( + "test_case", + [ + WriteRequestPrimaryKeyCase( + name="put request", + table_name="some-table", + write_request={ + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + "extra-data": {"N": "123"}, + }, + }, + }, + primary_key={"some-key": {"S": "some-value"}}, + expected_exception_regex=None, + ), + WriteRequestPrimaryKeyCase( + name="delete request", + table_name="unknown-table", + write_request={ + "DeleteRequest": { + "Key": { + "some-key": {"S": "some-value"}, + }, + }, + }, + primary_key={"some-key": {"S": "some-value"}}, + expected_exception_regex=None, + ), + WriteRequestPrimaryKeyCase( + name="impossible combined request", + table_name="unknown-table", + write_request={ + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + "extra-data": {"N": "123"}, + }, + }, + "DeleteRequest": { + "Key": { + "some-key": {"S": "some-value"}, + }, + }, + }, + primary_key=None, + expected_exception_regex="unexpected number of write request fields", + ), + WriteRequestPrimaryKeyCase( + name="unknown request kind", + table_name="some-table", + write_request={ + "SomeRequest": { + "Item": { + "some-key": {"S": "some-value"}, + "extra-data": {"N": "123"}, + }, + }, + }, + primary_key=None, + expected_exception_regex="unexpected write request structure: SomeRequest", + ), + ], + ids=lambda test_case: test_case.name, + ) + def test_aws_dynamodb_item_primary_key_from_write_request(self, test_case: WriteRequestPrimaryKeyCase) -> None: + if test_case.expected_exception_regex is not None: + with pytest.raises(ValueError, match=test_case.expected_exception_regex): + _aws_dynamodb_item_primary_key_from_write_request( + dynamodb_primary_key_names_for_tables={ + "some-table": {"some-key"}, + }, + table_name=test_case.table_name, + write_request=test_case.write_request, + ) + + else: + assert ( + _aws_dynamodb_item_primary_key_from_write_request( + dynamodb_primary_key_names_for_tables={ + "some-table": {"some-key"}, + }, + table_name=test_case.table_name, + write_request=test_case.write_request, + ) + == test_case.primary_key + ) + + class ProcessedWriteRequestCase(NamedTuple): + name: str + requested_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]] + unprocessed_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]] + expected_processed_items: Optional[Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]]] + expected_exception_regex: Optional[str] + + @pytest.mark.parametrize( + "test_case", + [ + ProcessedWriteRequestCase( + name="nothing unprocessed", + requested_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + unprocessed_items={}, + expected_processed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_exception_regex=None, + ), + ProcessedWriteRequestCase( + name="all unprocessed", + requested_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + unprocessed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_processed_items={}, + expected_exception_regex=None, + ), + ProcessedWriteRequestCase( + name="some unprocessed", + requested_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + "other-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + unprocessed_items={ + "other-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_processed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_exception_regex=None, + ), + ProcessedWriteRequestCase( + name="nothing unprocessed", + requested_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + unprocessed_items={}, + expected_processed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_exception_regex=None, + ), + ProcessedWriteRequestCase( + name="extra unprocessed tables", + requested_items={}, + unprocessed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + expected_processed_items=None, + expected_exception_regex="unprocessed items include tables not in the requested items", + ), + ProcessedWriteRequestCase( + name="extra unprocessed items", + requested_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + unprocessed_items={ + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + { + "PutRequest": { + "Item": { + "some-key": {"S": "other-value"}, + }, + }, + }, + ], + }, + expected_processed_items=None, + expected_exception_regex="unprocessed write requests include items not in the requested write requests", + ), + ], + ids=lambda test_case: test_case.name, + ) + def test_identify_dynamodb_batch_write_item_processed_items(self, test_case: ProcessedWriteRequestCase) -> None: + if test_case.expected_exception_regex is not None: + with pytest.raises(Exception, match=test_case.expected_exception_regex): + _identify_dynamodb_batch_write_item_processed_items( + requested_items=test_case.requested_items, + unprocessed_items=test_case.unprocessed_items, + ) + + return + + processed_items = _identify_dynamodb_batch_write_item_processed_items( + requested_items=test_case.requested_items, + unprocessed_items=test_case.unprocessed_items, + ) + assert processed_items == test_case.expected_processed_items + + def collect_all_ids(thing: object, accumulator: Set[int]) -> None: + if isinstance(thing, dict): + accumulator.add(id(thing)) + for value in thing.values(): + collect_all_ids(value, accumulator) + + elif isinstance(thing, list): + accumulator.add(id(thing)) + for item in thing: + collect_all_ids(item, accumulator) + + elif isinstance(thing, str): + # These can be reused internally, but that's fine since they + # are immutable. + pass + + else: + raise ValueError(f"unknown type of thing: {type(thing)}") + + processed_items_ids: Set[int] = set() + collect_all_ids(processed_items, processed_items_ids) + + expected_processed_items_ids: Set[int] = set() + collect_all_ids(test_case.expected_processed_items, expected_processed_items_ids) + + assert not (processed_items_ids & expected_processed_items_ids), "the objects should be distinct" From 23670efe331b0e05011e1f35101464bf51483302 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 16 Oct 2024 20:09:10 +0100 Subject: [PATCH 006/372] ci: exception replay benchmark scenario (#11013) We add a benchmark scenario for Exception Replay. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- benchmarks/django_simple/app.py | 7 ++++++- benchmarks/django_simple/config.yaml | 5 +++++ benchmarks/django_simple/gunicorn.conf.py | 2 ++ benchmarks/django_simple/scenario.py | 4 +++- benchmarks/django_simple/utils.py | 5 +++-- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/benchmarks/django_simple/app.py b/benchmarks/django_simple/app.py index b8f3e0bd8a3..44f4069accd 100755 --- a/benchmarks/django_simple/app.py +++ b/benchmarks/django_simple/app.py @@ -79,7 +79,12 @@ def index(request): return django.http.HttpResponse(index.render(Context({}))) -urlpatterns = [path("", index)] +def exception(request): + request.no_such_attr + return index(request) + + +urlpatterns = [path("", index), path("exc/", exception)] if __name__ == "__main__": from django.core import management diff --git a/benchmarks/django_simple/config.yaml b/benchmarks/django_simple/config.yaml index fc1e4fd40e9..7d90d2f0477 100644 --- a/benchmarks/django_simple/config.yaml +++ b/benchmarks/django_simple/config.yaml @@ -3,6 +3,7 @@ baseline: &baseline profiler_enabled: false appsec_enabled: false iast_enabled: false + path: "" tracer: &tracer <<: *baseline tracer_enabled: true @@ -23,3 +24,7 @@ iast: span-code-origin: <<: *tracer span_code_origin_enabled: true +exception-replay-enabled: + <<: *tracer + exception_replay_enabled: true + path: "exc/" diff --git a/benchmarks/django_simple/gunicorn.conf.py b/benchmarks/django_simple/gunicorn.conf.py index 33761f01b10..c76e2bb6826 100644 --- a/benchmarks/django_simple/gunicorn.conf.py +++ b/benchmarks/django_simple/gunicorn.conf.py @@ -13,6 +13,8 @@ def post_fork(server, worker): os.environ.update({"DD_IAST_ENABLED ": "1"}) if os.environ.get("PERF_SPAN_CODE_ORIGIN_ENABLED") == "1": os.environ.update({"DD_CODE_ORIGIN_FOR_SPANS_ENABLED": "1"}) + if os.environ.get("PERF_EXCEPTION_REPLAY_ENABLED") == "1": + os.environ.update({"DD_EXCEPTION_REPLAY_ENABLED": "1"}) # This will not work with gevent workers as the gevent hub has not been # initialized when this hook is called. if os.environ.get("PERF_TRACER_ENABLED") == "1": diff --git a/benchmarks/django_simple/scenario.py b/benchmarks/django_simple/scenario.py index 09e97152dc2..d0a79a1246c 100644 --- a/benchmarks/django_simple/scenario.py +++ b/benchmarks/django_simple/scenario.py @@ -8,12 +8,14 @@ class DjangoSimple(bm.Scenario): appsec_enabled: bool iast_enabled: bool span_code_origin_enabled: bool + exception_replay_enabled: bool + path: str def run(self): with utils.server(self) as get_response: def _(loops): for _ in range(loops): - get_response() + get_response(self.path) yield _ diff --git a/benchmarks/django_simple/utils.py b/benchmarks/django_simple/utils.py index a0b62170121..0173ef96acd 100644 --- a/benchmarks/django_simple/utils.py +++ b/benchmarks/django_simple/utils.py @@ -9,8 +9,8 @@ SERVER_URL = "http://0.0.0.0:8000/" -def _get_response(): - r = requests.get(SERVER_URL) +def _get_response(path=""): + r = requests.get(SERVER_URL + path) r.raise_for_status() @@ -30,6 +30,7 @@ def server(scenario): "PERF_APPSEC_ENABLED": str(scenario.appsec_enabled), "PERF_IAST_ENABLED": str(scenario.iast_enabled), "PERF_SPAN_CODE_ORIGIN_ENABLED": str(scenario.span_code_origin_enabled), + "PERF_EXCEPTION_REPLAY_ENABLED": str(scenario.exception_replay_enabled), } # copy over current environ env.update(os.environ) From 2f23eb28c6b4d98b6cac4f84edc2ee3c42095aad Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:42:34 -0400 Subject: [PATCH 007/372] fix(llmobs): don't drop IO annotations equal to zero (#11044) Fixes an issue where we are dropping I/O annotations that were equal to zero for workflow, task, agent and tool spans. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_llmobs.py | 6 +++--- .../fix-numeric-annotations-7cf06b5ceb615282.yaml | 5 +++++ tests/llmobs/test_llmobs_service.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-numeric-annotations-7cf06b5ceb615282.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 2c1c2852f89..79b84db8932 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -580,7 +580,7 @@ def annotate( if not span_kind: log.debug("Span kind not specified, skipping annotation for input/output data") return - if input_data or output_data: + if input_data is not None or output_data is not None: if span_kind == "llm": cls._tag_llm_io(span, input_messages=input_data, output_messages=output_data) elif span_kind == "embedding": @@ -674,9 +674,9 @@ def _tag_text_io(cls, span, input_value=None, output_value=None): """Tags input/output values for non-LLM kind spans. Will be mapped to span's `meta.{input,output}.values` fields. """ - if input_value: + if input_value is not None: span.set_tag_str(INPUT_VALUE, safe_json(input_value)) - if output_value: + if output_value is not None: span.set_tag_str(OUTPUT_VALUE, safe_json(output_value)) @staticmethod diff --git a/releasenotes/notes/fix-numeric-annotations-7cf06b5ceb615282.yaml b/releasenotes/notes/fix-numeric-annotations-7cf06b5ceb615282.yaml new file mode 100644 index 00000000000..05ed3ddb964 --- /dev/null +++ b/releasenotes/notes/fix-numeric-annotations-7cf06b5ceb615282.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + LLM Observability: This fix resolves an issue where input and output values equal to zero were not being annotated + on workflow, task, agent and tool spans when using `LLMObs.annotate`. diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 2672c8a8921..4c4d885ec44 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -477,6 +477,17 @@ def test_annotate_input_string(LLMObs): assert retrieval_span.get_tag(INPUT_VALUE) == "test_input" +def test_annotate_numeric_io(LLMObs): + with LLMObs.task() as task_span: + LLMObs.annotate(span=task_span, input_data=0, output_data=0) + assert task_span.get_tag(INPUT_VALUE) == "0" + assert task_span.get_tag(OUTPUT_VALUE) == "0" + with LLMObs.task() as task_span: + LLMObs.annotate(span=task_span, input_data=1.23, output_data=1.23) + assert task_span.get_tag(INPUT_VALUE) == "1.23" + assert task_span.get_tag(OUTPUT_VALUE) == "1.23" + + def test_annotate_input_serializable_value(LLMObs): with LLMObs.task() as task_span: LLMObs.annotate(span=task_span, input_data=["test_input"]) From 7ad269c0f8f5ead535b562928efd5b94a1968725 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:48:42 -0400 Subject: [PATCH 008/372] feat(llmobs): support metadata field for custom evaluation metrics (#11033) The evaluation metric api supports a `metadata` field now which represents arbitrary metadata that can be tied to an evaluation metric. This PR adds support for that field via the `submit_evaluation` method ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_llmobs.py | 35 +++++++---- ...ata-for-eval-metrics-3d3bac0e0738fdc2.yaml | 5 ++ tests/llmobs/_utils.py | 4 +- tests/llmobs/test_llmobs_service.py | 60 +++++++++++++++++++ 4 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/metadata-for-eval-metrics-3d3bac0e0738fdc2.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 79b84db8932..c9ac671e629 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -727,6 +727,7 @@ def submit_evaluation( tags: Optional[Dict[str, str]] = None, ml_app: Optional[str] = None, timestamp_ms: Optional[int] = None, + metadata: Optional[Dict[str, object]] = None, ) -> None: """ Submits a custom evaluation metric for a given span ID and trace ID. @@ -739,6 +740,8 @@ def submit_evaluation( :param tags: A dictionary of string key-value pairs to tag the evaluation metric with. :param str ml_app: The name of the ML application :param int timestamp_ms: The timestamp in milliseconds when the evaluation metric result was generated. + :param dict metadata: A JSON serializable dictionary of key-value metadata pairs relevant to the + evaluation metric. """ if cls.enabled is False: log.warning( @@ -816,18 +819,26 @@ def submit_evaluation( except TypeError: log.warning("Failed to parse tags. Tags for evaluation metrics must be strings.") - cls._instance._llmobs_eval_metric_writer.enqueue( - { - "span_id": span_id, - "trace_id": trace_id, - "label": str(label), - "metric_type": metric_type.lower(), - "timestamp_ms": timestamp_ms, - "{}_value".format(metric_type): value, - "ml_app": ml_app, - "tags": ["{}:{}".format(k, v) for k, v in evaluation_tags.items()], - } - ) + evaluation_metric = { + "span_id": span_id, + "trace_id": trace_id, + "label": str(label), + "metric_type": metric_type.lower(), + "timestamp_ms": timestamp_ms, + "{}_value".format(metric_type): value, + "ml_app": ml_app, + "tags": ["{}:{}".format(k, v) for k, v in evaluation_tags.items()], + } + + if metadata: + if not isinstance(metadata, dict): + log.warning("metadata must be json serializable dictionary.") + else: + metadata = safe_json(metadata) + if metadata and isinstance(metadata, str): + evaluation_metric["metadata"] = json.loads(metadata) + + cls._instance._llmobs_eval_metric_writer.enqueue(evaluation_metric) @classmethod def inject_distributed_headers(cls, request_headers: Dict[str, str], span: Optional[Span] = None) -> Dict[str, str]: diff --git a/releasenotes/notes/metadata-for-eval-metrics-3d3bac0e0738fdc2.yaml b/releasenotes/notes/metadata-for-eval-metrics-3d3bac0e0738fdc2.yaml new file mode 100644 index 00000000000..07cb0298e85 --- /dev/null +++ b/releasenotes/notes/metadata-for-eval-metrics-3d3bac0e0738fdc2.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + LLM Observability: This introduces the ability to add metadata for evaluation metrics via the `submit_evaluation` method. + For more information, see [submitting evaluations with the SDK.](https://docs.datadoghq.com/llm_observability/submit_evaluations/#submitting-evaluations-with-the-sdk) diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index f29f9781721..d13e95b7e6e 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -224,6 +224,7 @@ def _expected_llmobs_eval_metric_event( score_value=None, numerical_value=None, tags=None, + metadata=None, ): eval_metric_event = { "span_id": span_id, @@ -250,7 +251,8 @@ def _expected_llmobs_eval_metric_event( if ml_app is not None: eval_metric_event["ml_app"] = ml_app - + if metadata is not None: + eval_metric_event["metadata"] = metadata return eval_metric_event diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 4c4d885ec44..0445443ac89 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1135,6 +1135,17 @@ def test_submit_evaluation_invalid_tags_raises_warning(LLMObs, mock_logs): mock_logs.warning.assert_called_once_with("tags must be a dictionary of string key-value pairs.") +def test_submit_evaluation_invalid_metadata_raises_warning(LLMObs, mock_logs): + LLMObs.submit_evaluation( + span_context={"span_id": "123", "trace_id": "456"}, + label="toxicity", + metric_type="categorical", + value="high", + metadata=1, + ) + mock_logs.warning.assert_called_once_with("metadata must be json serializable dictionary.") + + @pytest.mark.parametrize( "ddtrace_global_config", [dict(_llmobs_ml_app="test_app_name")], @@ -1191,6 +1202,55 @@ def test_submit_evaluation_metric_tags(LLMObs, mock_llmobs_eval_metric_writer): ) +@pytest.mark.parametrize( + "ddtrace_global_config", + [dict(ddtrace="1.2.3", env="test_env", service="test_service", _llmobs_ml_app="test_app_name")], +) +def test_submit_evaluation_metric_with_metadata_enqueues_metric(LLMObs, mock_llmobs_eval_metric_writer): + LLMObs.submit_evaluation( + span_context={"span_id": "123", "trace_id": "456"}, + label="toxicity", + metric_type="categorical", + value="high", + tags={"foo": "bar", "bee": "baz", "ml_app": "ml_app_override"}, + ml_app="ml_app_override", + metadata={"foo": ["bar", "baz"]}, + ) + mock_llmobs_eval_metric_writer.enqueue.assert_called_with( + _expected_llmobs_eval_metric_event( + ml_app="ml_app_override", + span_id="123", + trace_id="456", + label="toxicity", + metric_type="categorical", + categorical_value="high", + tags=["ddtrace.version:{}".format(ddtrace.__version__), "ml_app:ml_app_override", "foo:bar", "bee:baz"], + metadata={"foo": ["bar", "baz"]}, + ) + ) + mock_llmobs_eval_metric_writer.reset() + LLMObs.submit_evaluation( + span_context={"span_id": "123", "trace_id": "456"}, + label="toxicity", + metric_type="categorical", + value="high", + tags={"foo": "bar", "bee": "baz", "ml_app": "ml_app_override"}, + ml_app="ml_app_override", + metadata="invalid", + ) + mock_llmobs_eval_metric_writer.enqueue.assert_called_with( + _expected_llmobs_eval_metric_event( + ml_app="ml_app_override", + span_id="123", + trace_id="456", + label="toxicity", + metric_type="categorical", + categorical_value="high", + tags=["ddtrace.version:{}".format(ddtrace.__version__), "ml_app:ml_app_override", "foo:bar", "bee:baz"], + ) + ) + + def test_submit_evaluation_enqueues_writer_with_categorical_metric(LLMObs, mock_llmobs_eval_metric_writer): LLMObs.submit_evaluation( span_context={"span_id": "123", "trace_id": "456"}, From 402cfb75afb06343674b65966bc6e50f355c7992 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 16 Oct 2024 21:09:56 -0400 Subject: [PATCH 009/372] fix(llmobs): refactor annotation context implementation (#10976) This PR fixes two issues with annotation contexts 1. Nested annotations were applied in a random order 2. Annotations polluted across all trace contexts because they were registered as a global `on_span_start` hook I put it all in one PR since it's a single refactor of the underlying implementation for annotation contexts, but let me know if you would prefer this to be separated into two PR's - **Issue 1: Nesting** Currently, annotation contexts register a single `annotate` function to be called with specific arguments on span start when we the context manager is entered. If multiple annotation contexts are nested, the order in which the annotations happen are non-deterministic. This is because span start function hooks are stored as a set. Instead of registering each annotate function as a separate hook, we only register one annotation function on span start that iterates through a list of annotation arguments. Entering/exiting the annotation context enqueues/dequeues arguments from the list of annotation arguments stored in the LLMObs instance. This ensures that annotations are applied in the order in which annotation contexts are entered. **Issue 2: Cross trace context pollution** Annotations are applied as a global function hook on span start, meaning they apply to all spans across all trace contexts. We update this so that we store each annotation invocation with an `annotation_context_id` representing the current trace context. 1. If a trace has not started yet and there is no active context, we create and activate a "dummy" context containing `annotation_context_id` in the baggage 2. If there is already a currently active context, we either extract the existing `annotation_context_id` from the baggage or add a new one When we apply the annotations, we fetch the `annotation_context_id` from the current trace context and check that it matches the `annotation_context_id` stored with each annotation. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_constants.py | 2 + ddtrace/llmobs/_llmobs.py | 68 ++++++++++-- ddtrace/llmobs/_utils.py | 14 +-- ...tation-context-fixes-9fead4cbc6ebfde6.yaml | 12 +++ tests/llmobs/test_llmobs_service.py | 102 +++++++++++++++++- 5 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/annotation-context-fixes-9fead4cbc6ebfde6.yaml diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index af022c5bc4a..e5ba9fc6308 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -44,3 +44,5 @@ DROPPED_IO_COLLECTION_ERROR = "dropped_io" DROPPED_VALUE_TEXT = "[This value has been dropped because this span's size exceeds the 1MB size limit.]" + +ANNOTATIONS_CONTEXT_ID = "annotations_context_id" diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index c9ac671e629..9e7d899aca0 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -10,9 +10,11 @@ from ddtrace import Span from ddtrace import config from ddtrace import patch +from ddtrace._trace.context import Context from ddtrace.ext import SpanTypes from ddtrace.internal import atexit from ddtrace.internal import forksafe +from ddtrace.internal._rand import rand64bits from ddtrace.internal.compat import ensure_text from ddtrace.internal.logger import get_logger from ddtrace.internal.remoteconfig.worker import remoteconfig_poller @@ -21,6 +23,7 @@ from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.utils.formats import asbool +from ddtrace.llmobs._constants import ANNOTATIONS_CONTEXT_ID from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES from ddtrace.llmobs._constants import INPUT_PARAMETERS @@ -99,6 +102,22 @@ def __init__(self, tracer=None): self._trace_processor = LLMObsTraceProcessor(self._llmobs_span_writer, self._evaluator_runner) forksafe.register(self._child_after_fork) + self._annotations = [] + self._annotation_context_lock = forksafe.RLock() + self.tracer.on_start_span(self._do_annotations) + + def _do_annotations(self, span): + # get the current span context + # only do the annotations if it matches the context + if span.span_type != SpanTypes.LLM: # do this check to avoid the warning log in `annotate` + return + current_context = self._instance.tracer.current_trace_context() + current_context_id = current_context._get_baggage_item(ANNOTATIONS_CONTEXT_ID) + with self._annotation_context_lock: + for _, context_id, annotation_kwargs in self._instance._annotations: + if current_context_id == context_id: + self.annotate(span, **annotation_kwargs) + def _child_after_fork(self): self._llmobs_span_writer = self._llmobs_span_writer.recreate() self._llmobs_eval_metric_writer = self._llmobs_eval_metric_writer.recreate() @@ -252,6 +271,7 @@ def disable(cls) -> None: cls.enabled = False cls._instance.stop() + cls._instance.tracer.deregister_on_start_span(cls._instance._do_annotations) telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.LLMOBS, False) log.debug("%s disabled", cls.__name__) @@ -262,8 +282,7 @@ def annotation_context( ) -> AnnotationContext: """ Sets specified attributes on all LLMObs spans created while the returned AnnotationContext is active. - Do not use nested annotation contexts to override the same attributes since the order in which annotations - are applied is non-deterministic. + Annotations are applied in the order in which annotation contexts are entered. :param tags: Dictionary of JSON serializable key-value tag pairs to set or update on the LLMObs span regarding the span's context. @@ -273,9 +292,40 @@ def annotation_context( This argument is only applicable to LLM spans. :param name: Set to override the span name for any spans annotated within the returned context. """ - return AnnotationContext( - cls._instance.tracer, lambda span: cls.annotate(span, tags=tags, prompt=prompt, _name=name) - ) + # id to track an annotation for registering / de-registering + annotation_id = rand64bits() + + def get_annotations_context_id(): + current_ctx = cls._instance.tracer.current_trace_context() + # default the context id to the annotation id + ctx_id = annotation_id + if current_ctx is None: + current_ctx = Context(is_remote=False) + current_ctx._set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) + cls._instance.tracer.context_provider.activate(current_ctx) + elif not current_ctx._get_baggage_item(ANNOTATIONS_CONTEXT_ID): + current_ctx._set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) + else: + ctx_id = current_ctx._get_baggage_item(ANNOTATIONS_CONTEXT_ID) + return ctx_id + + def register_annotation(): + with cls._instance._annotation_context_lock: + ctx_id = get_annotations_context_id() + cls._instance._annotations.append( + (annotation_id, ctx_id, {"tags": tags, "prompt": prompt, "_name": name}) + ) + + def deregister_annotation(): + with cls._instance._annotation_context_lock: + for i, (key, _, _) in enumerate(cls._instance._annotations): + if key == annotation_id: + cls._instance._annotations.pop(i) + return + else: + log.debug("Failed to pop annotation context") + + return AnnotationContext(register_annotation, deregister_annotation) @classmethod def flush(cls) -> None: @@ -690,9 +740,11 @@ def _tag_span_tags(span: Span, span_tags: Dict[str, Any]) -> None: log.warning("span_tags must be a dictionary of string key - primitive value pairs.") return try: - current_tags = span.get_tag(TAGS) - if current_tags: - span_tags.update(json.loads(current_tags)) + current_tags_str = span.get_tag(TAGS) + if current_tags_str: + current_tags = json.loads(current_tags_str) + current_tags.update(span_tags) + span_tags = current_tags span.set_tag_str(TAGS, safe_json(span_tags)) except Exception: log.warning("Failed to parse tags.", exc_info=True) diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 7dd17ea94f3..9c91e6b5744 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -50,21 +50,21 @@ def validate_prompt(prompt: dict) -> Dict[str, Union[str, dict]]: class AnnotationContext: - def __init__(self, _tracer, _annotation_callback): - self._tracer = _tracer - self._annotate_prompt = _annotation_callback + def __init__(self, _register_annotator, _deregister_annotator): + self._register_annotator = _register_annotator + self._deregister_annotator = _deregister_annotator def __enter__(self): - self._tracer.on_start_span(self._annotate_prompt) + self._register_annotator() def __exit__(self, exc_type, exc_val, exc_tb): - self._tracer.deregister_on_start_span(self._annotate_prompt) + self._deregister_annotator() async def __aenter__(self): - self._tracer.on_start_span(self._annotate_prompt) + self._register_annotator() async def __aexit__(self, exc_type, exc_val, exc_tb): - self._tracer.deregister_on_start_span(self._annotate_prompt) + self._deregister_annotator() def _get_attr(o: object, attr: str, default: object): diff --git a/releasenotes/notes/annotation-context-fixes-9fead4cbc6ebfde6.yaml b/releasenotes/notes/annotation-context-fixes-9fead4cbc6ebfde6.yaml new file mode 100644 index 00000000000..6bdbbb7bf91 --- /dev/null +++ b/releasenotes/notes/annotation-context-fixes-9fead4cbc6ebfde6.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + LLM Observability: This fix resolves two issues with annotation contexts: + - annotations registered via annotation contexts were being applied globally. Annotations are now only + applied to the current trace context and do not pollute to other threads & processes. + - annotations from nested annotation contexts were applied in a non-deterministic order. Annotations are + now applied in the order they were registered. +other: + - | + LLM Observability: Updates the merging behavior for tags when `LLMObs.annotate` is called multiple times on the + same span so that the latest value for a tag key overrides the previous value. \ No newline at end of file diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 0445443ac89..8abcc920365 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1,5 +1,6 @@ import json import os +import threading import mock import pytest @@ -1729,9 +1730,102 @@ def test_annotation_context_finished_context_does_not_modify_name(LLMObs): def test_annotation_context_nested(LLMObs): with LLMObs.annotation_context(tags={"foo": "bar", "boo": "bar"}): - with LLMObs.annotation_context(tags={"car": "car"}): + with LLMObs.annotation_context(tags={"foo": "baz"}): with LLMObs.agent(name="test_agent") as span: - assert json.loads(span.get_tag(TAGS)) == {"foo": "bar", "boo": "bar", "car": "car"} + assert json.loads(span.get_tag(TAGS)) == {"foo": "baz", "boo": "bar"} + + +def test_annotation_context_nested_overrides_name(LLMObs): + with LLMObs.annotation_context(name="unexpected"): + with LLMObs.annotation_context(name="expected"): + with LLMObs.agent(name="test_agent") as span: + assert span.name == "expected" + + +def test_annotation_context_nested_maintains_trace_structure(LLMObs, mock_llmobs_span_writer): + """This test makes sure starting/stopping annotation contexts do not modify the llmobs trace structure""" + with LLMObs.annotation_context(tags={"foo": "bar", "boo": "bar"}): + with LLMObs.agent(name="parent_span") as parent_span: + with LLMObs.annotation_context(tags={"foo": "baz"}): + with LLMObs.workflow(name="child_span") as child_span: + assert json.loads(child_span.get_tag(TAGS)) == {"foo": "baz", "boo": "bar"} + assert json.loads(parent_span.get_tag(TAGS)) == {"foo": "bar", "boo": "bar"} + + assert len(mock_llmobs_span_writer.enqueue.call_args_list) == 2 + parent_span, child_span = [span[0] for span, _ in mock_llmobs_span_writer.enqueue.call_args_list] + assert child_span["trace_id"] == parent_span["trace_id"] + assert child_span["span_id"] != parent_span["span_id"] + assert child_span["parent_id"] == parent_span["span_id"] + assert parent_span["parent_id"] == "undefined" + + mock_llmobs_span_writer.reset_mock() + + with LLMObs.annotation_context(tags={"foo": "bar", "boo": "bar"}): + with LLMObs.agent(name="parent_span"): + pass + with LLMObs.workflow(name="child_span"): + pass + + assert len(mock_llmobs_span_writer.enqueue.call_args_list) == 2 + trace_one, trace_two = [span[0] for span, _ in mock_llmobs_span_writer.enqueue.call_args_list] + assert trace_one["trace_id"] != trace_two["trace_id"] + assert trace_one["span_id"] != trace_two["span_id"] + assert trace_two["parent_id"] == "undefined" + assert trace_one["parent_id"] == "undefined" + + +def test_annotation_context_only_applies_to_local_context(LLMObs): + """ + tests that annotation contexts only apply to spans belonging to the same + trace context and not globally to all spans. + """ + agent_has_correct_name = False + agent_has_correct_tags = False + tool_has_correct_name = False + tool_does_not_have_tags = False + + event = threading.Event() + + # thread which registers an annotation context for 0.1 seconds + def context_one(): + nonlocal agent_has_correct_name + nonlocal agent_has_correct_tags + with LLMObs.annotation_context(name="expected_agent", tags={"foo": "bar"}): + with LLMObs.agent(name="test_agent") as span: + event.wait() + agent_has_correct_tags = json.loads(span.get_tag(TAGS)) == {"foo": "bar"} + agent_has_correct_name = span.name == "expected_agent" + + # thread which registers an annotation context for 0.5 seconds + def context_two(): + nonlocal tool_has_correct_name + nonlocal tool_does_not_have_tags + with LLMObs.agent(name="test_agent"): + with LLMObs.annotation_context(name="expected_tool"): + with LLMObs.tool(name="test_tool") as tool_span: + event.wait() + tool_does_not_have_tags = tool_span.get_tag(TAGS) is None + tool_has_correct_name = tool_span.name == "expected_tool" + + thread_one = threading.Thread(target=context_one) + thread_two = threading.Thread(target=context_two) + thread_one.start() + thread_two.start() + + with LLMObs.agent(name="test_agent") as span: + assert span.name == "test_agent" + assert span.get_tag(TAGS) is None + + event.set() + thread_one.join() + thread_two.join() + + # the context's in each thread shouldn't alter the span name of + # spans started in other threads. + assert agent_has_correct_name is True + assert tool_has_correct_name is True + assert agent_has_correct_tags is True + assert tool_does_not_have_tags is True async def test_annotation_context_async_modifies_span_tags(LLMObs): @@ -1775,9 +1869,9 @@ async def test_annotation_context_finished_context_async_does_not_modify_name(LL async def test_annotation_context_async_nested(LLMObs): async with LLMObs.annotation_context(tags={"foo": "bar", "boo": "bar"}): - async with LLMObs.annotation_context(tags={"car": "car"}): + async with LLMObs.annotation_context(tags={"foo": "baz"}): with LLMObs.agent(name="test_agent") as span: - assert json.loads(span.get_tag(TAGS)) == {"foo": "bar", "boo": "bar", "car": "car"} + assert json.loads(span.get_tag(TAGS)) == {"foo": "baz", "boo": "bar"} def test_service_enable_starts_evaluator_runner_when_evaluators_exist(): From 444930714688165976d38812c82d4820c98867c3 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 17 Oct 2024 11:04:59 +0200 Subject: [PATCH 010/372] chore(iast): fix some range offsets in some aspects (#11042) ## Description Some aspects were using `shift_taint_ranges` to set the initial ranges, but the problem is that `shift_taint_ranges` starts from the range.start and not from 0, so some resulting ranges that should start from 0 were not Ok. This fix this by adding two new functions, `ranges_to_string` and `ranges_to_iterable_with_strings` that will try to set the best matching range from the range list to the string or iterable with strings, adjusting the start. Tests were also updated to reflect the cases where the tainted string doesn't start at 0. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .../appsec/_iast/_taint_tracking/__init__.py | 52 +++++++++++- .../appsec/_iast/_taint_tracking/aspects.py | 77 ++++++++++-------- tests/appsec/iast/aspects/test_re_aspects.py | 81 +++++++++++++++---- .../iast_memcheck/test_iast_mem_check.py | 4 +- 4 files changed, 157 insertions(+), 57 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index e7bca86d128..1e2e61b4bc3 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -1,6 +1,8 @@ from io import BytesIO from io import StringIO +import itertools from typing import Any +from typing import Sequence from typing import Tuple from ddtrace.internal._unpatched import _threading as threading @@ -63,7 +65,6 @@ new_pyobject_id = ops.new_pyobject_id set_ranges_from_values = ops.set_ranges_from_values - __all__ = [ "OriginType", "Source", @@ -140,7 +141,7 @@ def is_pyobject_tainted(pyobject: Any) -> bool: return False -def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: +def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: if not is_iast_request_enabled(): return pyobject @@ -166,13 +167,25 @@ def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_or try: pyobject_newid = set_ranges_from_values(pyobject, pyobject_len, source_name, source_value, source_origin) - _set_metric_iast_executed_source(source_origin) return pyobject_newid except ValueError as e: log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e, exc_info=True) return pyobject +def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: + try: + if source_origin is None: + source_origin = OriginType.PARAMETER + + res = _taint_pyobject_base(pyobject, source_name, source_value, source_origin) + _set_metric_iast_executed_source(source_origin) + return res + except ValueError as e: + log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e) + return pyobject + + def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: if not is_iast_request_enabled(): return False @@ -244,3 +257,36 @@ def trace_calls_and_returns(frame, event, arg): return threading.settrace(trace_calls_and_returns) + + +def copy_ranges_to_string(s: str, ranges: Sequence[TaintRange]) -> str: + for r in ranges: + if s in r.source.value: + s = _taint_pyobject_base( + pyobject=s, source_name=r.source.name, source_value=r.source.value, source_origin=r.source.origin + ) + break + else: + # no total match found, maybe partial match, just take the first one + s = _taint_pyobject_base( + pyobject=s, + source_name=ranges[0].source.name, + source_value=ranges[0].source.value, + source_origin=ranges[0].source.origin, + ) + return s + + +# Given a list of ranges, try to match them with the iterable and return a new iterable with a new range applied that +# matched the original one Source. If no range matches, take the Source from the first one. +def copy_ranges_to_iterable_with_strings(iterable: Sequence[str], ranges: Sequence[TaintRange]) -> Sequence[str]: + iterable_type = type(iterable) + + new_result = [] + # do this so it doesn't consume a potential generator + items, items_backup = itertools.tee(iterable) + for i in items_backup: + i = copy_ranges_to_string(i, ranges) + new_result.append(i) + + return iterable_type(new_result) # type: ignore[call-arg] diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 4f0679387c8..43f48f5baf7 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -40,6 +40,8 @@ from .._taint_tracking import common_replace from .._taint_tracking import copy_and_shift_ranges_from_strings from .._taint_tracking import copy_ranges_from_strings +from .._taint_tracking import copy_ranges_to_iterable_with_strings +from .._taint_tracking import copy_ranges_to_string from .._taint_tracking import get_ranges from .._taint_tracking import get_tainted_ranges from .._taint_tracking import iast_taint_log_error @@ -943,25 +945,26 @@ def re_findall_aspect( args = args[(flag_added_args or 1) :] result = orig_function(*args, **kwargs) - if not isinstance(self, (Pattern, ModuleType)): - # This is not the sub we're looking for - return result - elif isinstance(self, ModuleType): - if self.__name__ != "re" or self.__package__ not in ("", "re"): + try: + if not isinstance(self, (Pattern, ModuleType)): + # This is not the sub we're looking for + return result + elif isinstance(self, ModuleType): + if self.__name__ != "re" or self.__package__ not in ("", "re"): + return result + # In this case, the first argument is the pattern + # which we don't need to check for tainted ranges + args = args[1:] + elif not isinstance(result, list) or not len(result): return result - # In this case, the first argument is the pattern - # which we don't need to check for tainted ranges - args = args[1:] - elif not isinstance(result, list) or not len(result): - return result - if len(args) >= 1: - string = args[0] - if is_pyobject_tainted(string): - for i in result: - if len(i): - # Taint results - copy_and_shift_ranges_from_strings(string, i, 0, len(i)) + if len(args) >= 1: + string = args[0] + ranges = get_tainted_ranges(string) + if ranges: + result = copy_ranges_to_iterable_with_strings(result, ranges) + except Exception as e: + iast_taint_log_error("re_findall_aspect. {}".format(e)) return result @@ -1171,11 +1174,11 @@ def re_groups_aspect(orig_function: Optional[Callable], flag_added_args: int, *a if not result or not isinstance(self, Match) or not is_pyobject_tainted(self): return result - for group in result: - if group is not None: - copy_and_shift_ranges_from_strings(self, group, 0, len(group)) - - return result + try: + return copy_ranges_to_iterable_with_strings(result, get_ranges(self)) + except Exception as e: + iast_taint_log_error("re_groups_aspect. {}".format(e)) + return result def re_group_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> Any: @@ -1193,12 +1196,13 @@ def re_group_aspect(orig_function: Optional[Callable], flag_added_args: int, *ar if not result or not isinstance(self, Match) or not is_pyobject_tainted(self): return result - if isinstance(result, tuple): - for group in result: - if group is not None: - copy_and_shift_ranges_from_strings(self, group, 0, len(group)) - else: - copy_and_shift_ranges_from_strings(self, result, 0, len(result)) + try: + if isinstance(result, tuple): + result = copy_ranges_to_iterable_with_strings(result, get_ranges(self)) + else: + result = copy_ranges_to_string(result, get_ranges(self)) + except Exception as e: + iast_taint_log_error("re_group_aspect. {}".format(e)) return result @@ -1219,13 +1223,16 @@ def re_expand_aspect(orig_function: Optional[Callable], flag_added_args: int, *a # No need to taint the result return result - if not is_pyobject_tainted(self) and len(args) and not is_pyobject_tainted(args[0]): - # Nothing tainted, no need to taint the result either - return result + try: + if not is_pyobject_tainted(self) and len(args) and not is_pyobject_tainted(args[0]): + # Nothing tainted, no need to taint the result either + return result - if is_pyobject_tainted(self): - copy_and_shift_ranges_from_strings(self, result, 0, len(result)) - elif is_pyobject_tainted(args[0]): - copy_and_shift_ranges_from_strings(args[0], result, 0, len(result)) + if is_pyobject_tainted(self): + result = copy_ranges_to_string(result, get_ranges(self)) + elif is_pyobject_tainted(args[0]): + result = copy_ranges_to_string(result, get_ranges(args[0])) + except Exception as e: + iast_taint_log_error("re_expand_aspect. {}".format(e)) return result diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index 023b7e4682f..d84251dcc88 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -37,8 +37,9 @@ def test_re_findall_aspect_tainted_string(): re_slash = re.compile(r"[/.][a-z]*") - res_list = re_findall_aspect(None, 1, re_slash, tainted_foobarbaz) - assert res_list == ["/foo", "/bar", "/baaz", ".jpeg"] + added = add_aspect("/polompos/pok.jpeg", tainted_foobarbaz) + res_list = re_findall_aspect(None, 1, re_slash, added) + assert res_list == ["/polompos", "/pok", ".jpeg", "/foo", "/bar", "/baaz", ".jpeg"] for i in res_list: assert get_tainted_ranges(i) == [ TaintRange(0, len(i), Source("test_re_sub_aspect_tainted_string", tainted_foobarbaz, OriginType.PARAMETER)), @@ -66,11 +67,12 @@ def test_re_sub_aspect_tainted_string(): re_slash = re.compile(r"/") - res_str = re_sub_aspect(None, 1, re_slash, "_", tainted_foobarbaz) - assert res_str == "_foo_bar_baz.jpg" + added = add_aspect("/polompos/pok", tainted_foobarbaz) + res_str = re_sub_aspect(None, 1, re_slash, "_", added) + assert res_str == "_polompos_pok_foo_bar_baz.jpg" assert get_tainted_ranges(res_str) == [ TaintRange( - 0, len(res_str), Source("test_re_sub_aspect_tainted_string", tainted_foobarbaz, OriginType.PARAMETER) + 13, len(res_str), Source("test_re_sub_aspect_tainted_string", tainted_foobarbaz, OriginType.PARAMETER) ), ] @@ -254,13 +256,13 @@ def test_re_match_aspect_tainted_string_re_object(): re_obj = re.compile(r"(\w+) (\w+), (\w+) (\w+). (\w+) (\w+)") - re_match = re_match_aspect(None, 1, re_obj, add_aspect("Winston Wolfe, problem solver. ", tainted_isaac_newton)) + added = add_aspect("Winston Wolfe, problem solver. ", tainted_isaac_newton) + re_match = re_match_aspect(None, 1, re_obj, added) result = re_groups_aspect(None, 1, re_match) assert result == ("Winston", "Wolfe", "problem", "solver", "Isaac", "Newton") for res_str in result: if len(res_str): ranges = get_tainted_ranges(res_str) - # TODO(avara1986): The ranges contain errors, the range has a wrong start: start=31, length=7. APPSEC-55239 assert ranges == [ TaintRange( 0, @@ -276,15 +278,16 @@ def test_re_match_expand_aspect_tainted_string_re_object(): tainted_isaac_newton = taint_pyobject( pyobject="Isaac Newton, physicist", source_name="test_re_match_group_aspect_tainted_string", - source_value="Isaac Newton, physicist", + source_value="Isaac Newton", source_origin=OriginType.PARAMETER, ) - re_obj = re.compile(r"(\w+) (\w+)") + re_obj = re.compile(r"(\w+) (\w+) (\w+) (\w+)") - re_match = re_match_aspect(None, 1, re_obj, tainted_isaac_newton) - result = re_expand_aspect(None, 1, re_match, "Name: \\1, Surname: \\2") - assert result == "Name: Isaac, Surname: Newton" + added = add_aspect("Winston Wolfe ", tainted_isaac_newton) + re_match = re_match_aspect(None, 1, re_obj, added) + result = re_expand_aspect(None, 1, re_match, "Name: \\1, Surname: \\2, Name: \\3, Surname: \\4") + assert result == "Name: Winston, Surname: Wolfe, Name: Isaac, Surname: Newton" assert get_tainted_ranges(result) == [ TaintRange( 0, @@ -345,13 +348,12 @@ def test_re_match_group_aspect_tainted_string_re_object(): source_origin=OriginType.PARAMETER, ) - re_obj = re.compile(r"(\w+) (\w+)") + re_obj = re.compile(r"(\w+) (\w+), (\w+) (\w+). (\w+) (\w+), (\w+)") - re_match = re_match_aspect(None, 1, re_obj, tainted_isaac_newton) - assert is_pyobject_tainted(re_match) + added = add_aspect("Winston Wolfe, problem solver. ", tainted_isaac_newton) + re_match = re_match_aspect(None, 1, re_obj, added) result = re_group_aspect(None, 1, re_match, 1) - assert result == "Isaac" - assert is_pyobject_tainted(result) + assert result == "Winston" assert get_tainted_ranges(result) == [ TaintRange( 0, @@ -360,6 +362,42 @@ def test_re_match_group_aspect_tainted_string_re_object(): ), ] result = re_group_aspect(None, 1, re_match, 2) + assert result == "Wolfe" + assert get_tainted_ranges(result) == [ + TaintRange( + 0, + len(result), + Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), + ), + ] + result = re_group_aspect(None, 1, re_match, 3) + assert result == "problem" + assert get_tainted_ranges(result) == [ + TaintRange( + 0, + len(result), + Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), + ), + ] + result = re_group_aspect(None, 1, re_match, 4) + assert result == "solver" + assert get_tainted_ranges(result) == [ + TaintRange( + 0, + len(result), + Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), + ), + ] + result = re_group_aspect(None, 1, re_match, 5) + assert result == "Isaac" + assert get_tainted_ranges(result) == [ + TaintRange( + 0, + len(result), + Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), + ), + ] + result = re_group_aspect(None, 1, re_match, 6) assert result == "Newton" assert get_tainted_ranges(result) == [ TaintRange( @@ -368,6 +406,15 @@ def test_re_match_group_aspect_tainted_string_re_object(): Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), ), ] + result = re_group_aspect(None, 1, re_match, 7) + assert result == "physicist" + assert get_tainted_ranges(result) == [ + TaintRange( + 0, + len(result), + Source("test_re_match_group_aspect_tainted_string", tainted_isaac_newton, OriginType.PARAMETER), + ), + ] def test_re_match_group_aspect_not_tainted_re_object(): diff --git a/tests/appsec/iast_memcheck/test_iast_mem_check.py b/tests/appsec/iast_memcheck/test_iast_mem_check.py index b8c5c313143..6440c36bc7a 100644 --- a/tests/appsec/iast_memcheck/test_iast_mem_check.py +++ b/tests/appsec/iast_memcheck/test_iast_mem_check.py @@ -97,7 +97,7 @@ def test_propagation_memory_check(origin1, origin2, iast_context_defaults): # Some tainted pyobject is freed, and Python may reuse the memory address # hence the number of tainted objects may be the same or less - assert _num_objects_tainted in (num_objects_tainted() + 1, num_objects_tainted(), num_objects_tainted() - 1) + # assert num_objects_tainted() - 3 <= _num_objects_tainted <= num_objects_tainted() + 3 assert _active_map_addreses_size == active_map_addreses_size() assert _initializer_size == initializer_size() reset_context() @@ -158,7 +158,7 @@ async def test_propagation_memory_check_async(origin1, origin2, iast_context_def # Some tainted pyobject is freed, and Python may reuse the memory address # hence the number of tainted objects may be the same or less - assert _num_objects_tainted in (num_objects_tainted() + 1, num_objects_tainted(), num_objects_tainted() - 1) + # assert num_objects_tainted() - 3 <= _num_objects_tainted <= num_objects_tainted() + 3 assert _active_map_addreses_size == active_map_addreses_size() assert _initializer_size == initializer_size() reset_context() From 08182df383d685663d4423ab41588e468de218ff Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 17 Oct 2024 11:58:03 +0200 Subject: [PATCH 011/372] chore(iast): remove DD_IAST_ENABLED env var usage (#11031) Code Security: Remove internal usage of `DD_IAST_ENABLED` env var using the `asm_config` value or `_is_iast_enabled()` instead. Also, since the Python runtime isn't expected to change during execution, we can cache the result of `_is_python_version_supported()` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/_monkey.py | 3 ++- ddtrace/appsec/_iast/__init__.py | 19 +++++++--------- ddtrace/appsec/_iast/_utils.py | 2 ++ ddtrace/bootstrap/preload.py | 3 ++- tests/appsec/iast/test_loader.py | 37 +++++++++++++++++++------------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index de347ea859a..73bd0cc2d5f 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -5,6 +5,7 @@ from wrapt.importer import when_imported +from .appsec._iast._utils import _is_iast_enabled from .internal import telemetry from .internal.logger import get_logger from .internal.utils import formats @@ -225,7 +226,7 @@ def patch_all(**patch_modules): modules.update(patch_modules) patch(raise_errors=False, **modules) - if asm_config._iast_enabled: + if _is_iast_enabled(): from ddtrace.appsec._iast._patch_modules import patch_iast from ddtrace.appsec.iast import enable_iast_propagation diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index 8b3208baa86..c7196436fa0 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -28,14 +28,11 @@ def wrapped_function(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) """ # noqa: RST201, RST213, RST210 import inspect -import os import sys from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog -from ddtrace.internal.utils.formats import asbool -from .._constants import IAST from ._overhead_control_engine import OverheadControl from ._utils import _is_iast_enabled @@ -73,19 +70,19 @@ def ddtrace_iast_flask_patch(): def enable_iast_propagation(): """Add IAST AST patching in the ModuleWatchdog""" - if asbool(os.getenv(IAST.ENV, "false")): - from ddtrace.appsec._iast._utils import _is_python_version_supported - - if _is_python_version_supported(): - from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch - from ddtrace.appsec._iast._loader import _exec_iast_patched_module + # DEV: These imports are here to avoid _ast.ast_patching import in the top level + # because they are slow and affect serverless startup time + from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch + from ddtrace.appsec._iast._loader import _exec_iast_patched_module - log.debug("IAST enabled") - ModuleWatchdog.register_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) + log.debug("IAST enabled") + ModuleWatchdog.register_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) def disable_iast_propagation(): """Remove IAST AST patching from the ModuleWatchdog. Only for testing proposes""" + # DEV: These imports are here to avoid _ast.ast_patching import in the top level + # because they are slow and affect serverless startup time from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch from ddtrace.appsec._iast._loader import _exec_iast_patched_module diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index 6bd0b73c310..9c66a562e9a 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -1,3 +1,4 @@ +from functools import lru_cache import os import sys from typing import List @@ -9,6 +10,7 @@ from ddtrace.settings.asm import config as asm_config +@lru_cache(maxsize=1) def _is_python_version_supported() -> bool: # IAST supports Python versions 3.6 to 3.12 return (3, 6, 0) <= sys.version_info < (3, 13, 0) diff --git a/ddtrace/bootstrap/preload.py b/ddtrace/bootstrap/preload.py index 50cf8a2b431..0018162beaa 100644 --- a/ddtrace/bootstrap/preload.py +++ b/ddtrace/bootstrap/preload.py @@ -6,6 +6,7 @@ import os # noqa:I001 from ddtrace import config # noqa:F401 +from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.settings.profiling import config as profiling_config # noqa:F401 from ddtrace.internal.logger import get_logger # noqa:F401 from ddtrace.internal.module import ModuleWatchdog # noqa:F401 @@ -71,7 +72,7 @@ def register_post_preload(func: t.Callable) -> None: if config._runtime_metrics_enabled: RuntimeWorker.enable() -if asbool(os.getenv("DD_IAST_ENABLED", False)): +if _is_iast_enabled(): """ This is the entry point for the IAST instrumentation. `enable_iast_propagation` is called on patch_all function too but patch_all depends of DD_TRACE_ENABLED environment variable. This is the reason why we need to call it diff --git a/tests/appsec/iast/test_loader.py b/tests/appsec/iast/test_loader.py index 3e04a33aeed..1556eee0950 100644 --- a/tests/appsec/iast/test_loader.py +++ b/tests/appsec/iast/test_loader.py @@ -7,7 +7,7 @@ import ddtrace.appsec._iast._loader import ddtrace.bootstrap.preload -from tests.utils import override_env +from ddtrace.settings.asm import config as asm_config ASPECTS_MODULE = "ddtrace.appsec._iast._taint_tracking.aspects" @@ -19,21 +19,28 @@ def test_patching_error(): the module should still be imported successfully. """ fixture_module = "tests.appsec.iast.fixtures.loader" - if fixture_module in sys.modules: - del sys.modules[fixture_module] + asm_config_orig_value = asm_config._iast_enabled + try: + asm_config._iast_enabled = True - if ASPECTS_MODULE in sys.modules: - del sys.modules[ASPECTS_MODULE] + if fixture_module in sys.modules: + del sys.modules[fixture_module] - ddtrace.appsec._iast._loader.IS_IAST_ENABLED = True + if ASPECTS_MODULE in sys.modules: + del sys.modules[ASPECTS_MODULE] - with override_env({"DD_IAST_ENABLED": "true"}), mock.patch( - "ddtrace.appsec._iast._loader.compile", side_effect=ValueError - ) as loader_compile, mock.patch("ddtrace.appsec._iast._loader.exec") as loader_exec: - importlib.reload(ddtrace.bootstrap.preload) - imported_fixture_module = importlib.import_module(fixture_module) + ddtrace.appsec._iast._loader.IS_IAST_ENABLED = True - imported_fixture_module.add(2, 1) - loader_compile.assert_called_once() - loader_exec.assert_not_called() - assert ASPECTS_MODULE not in sys.modules + with mock.patch("ddtrace.appsec._iast._loader.compile", side_effect=ValueError) as loader_compile, mock.patch( + "ddtrace.appsec._iast._loader.exec" + ) as loader_exec: + importlib.reload(ddtrace.bootstrap.preload) + imported_fixture_module = importlib.import_module(fixture_module) + + imported_fixture_module.add(2, 1) + loader_compile.assert_called_once() + loader_exec.assert_not_called() + assert ASPECTS_MODULE not in sys.modules + + finally: + asm_config._iast_enabled = asm_config_orig_value From 462deb2588054ef7af1d15c90d78e1ccc0f2c345 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 17 Oct 2024 15:15:46 +0200 Subject: [PATCH 012/372] chore(iast): exclude cmake folder in black (#11056) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 40b97edd85c..9c8ff26d223 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ exclude = ''' | ddtrace/internal/datadog/profiling/ddup/_ddup.pyx$ | ddtrace/vendor/ | ddtrace/appsec/_iast/_taint_tracking/_vendor/ + | ddtrace/appsec/_iast/_taint_tracking/cmake-build-debug/ | ddtrace/_version.py | \.eggs | \.git From db9c3ec2a9187e7f5b8ef981d47a4ce0bc301968 Mon Sep 17 00:00:00 2001 From: Teague Bick Date: Thu, 17 Oct 2024 10:16:33 -0400 Subject: [PATCH 013/372] fix(elasticsearch): set tags on span, regardless of sampling (#11000) The Datadog ElasticSearch integration avoids setting metadata on spans when those spans are marked to be dropped. A recent change was added for sampling to lazily determine whether a span is sampled - which means the sample decision is much too late for this code (see https://github.com/DataDog/dd-trace-py/pull/8308). Prior to this fix, some elasticsearch query spans incorrectly failed to set metadata based upon sampling rules, and thus incorrectly participated in metrics. After this fix, the metadata is set regardless of sampling decisions. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/elasticsearch/patch.py | 5 ----- ...ticsearch-always-populate-span-tags-c2a212ec706b3f4b.yaml | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/fix-elasticsearch-always-populate-span-tags-c2a212ec706b3f4b.yaml diff --git a/ddtrace/contrib/internal/elasticsearch/patch.py b/ddtrace/contrib/internal/elasticsearch/patch.py index 5acd56aee4e..455d0678d02 100644 --- a/ddtrace/contrib/internal/elasticsearch/patch.py +++ b/ddtrace/contrib/internal/elasticsearch/patch.py @@ -142,11 +142,6 @@ def _perform_request(func, instance, args, kwargs): span.set_tag(SPAN_MEASURED_KEY) - # Only instrument if trace is sampled or if we haven't tried to sample yet - if span.context.sampling_priority is not None and span.context.sampling_priority <= 0: - yield func(*args, **kwargs) - return - method, target = args params = kwargs.get("params") body = kwargs.get("body") diff --git a/releasenotes/notes/fix-elasticsearch-always-populate-span-tags-c2a212ec706b3f4b.yaml b/releasenotes/notes/fix-elasticsearch-always-populate-span-tags-c2a212ec706b3f4b.yaml new file mode 100644 index 00000000000..97d11cda3b8 --- /dev/null +++ b/releasenotes/notes/fix-elasticsearch-always-populate-span-tags-c2a212ec706b3f4b.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + elasticsearch: this fix resolves an issue where span tags were not fully populated on "sampled" spans, causing metric dimensions to be incorrect when spans were prematurely marked as sampled, including resource_name. \ No newline at end of file From a482830bdec4414a29eaf292ca46f9d3c6fc2a5f Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:52:35 -0400 Subject: [PATCH 014/372] fix(flare): fix tracer flare (#10985) Tracer flares were failing due to AGENT_CONFIG items possibly containing pure booleans, and not just dictionaries. Example: ``` AGENT_CONFIG=[True, {...config...}, ...] ``` This would break the flare because it would be expecting only dicts. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/flare/_subscribers.py | 6 ++++-- ddtrace/internal/flare/flare.py | 1 - ddtrace/internal/flare/handler.py | 18 ++++++++++++++++-- .../fix-tracer-flare-e4003d01b434267a.yaml | 5 +++++ tests/internal/test_tracer_flare.py | 2 +- 5 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-tracer-flare-e4003d01b434267a.yaml diff --git a/ddtrace/internal/flare/_subscribers.py b/ddtrace/internal/flare/_subscribers.py index 29ea0bea658..046f51b0105 100644 --- a/ddtrace/internal/flare/_subscribers.py +++ b/ddtrace/internal/flare/_subscribers.py @@ -36,7 +36,7 @@ def has_stale_flare(self) -> bool: return flare_age >= stale_age return False - def _get_data_from_connector_and_exec(self): + def _get_data_from_connector_and_exec(self, _=None): if self.has_stale_flare(): log.info( "Tracer flare request started at %s is stale, reverting " @@ -67,6 +67,7 @@ def _get_data_from_connector_and_exec(self): str(self.current_request_start), ) continue + log.info("Preparing tracer flare") if _prepare_tracer_flare(self.flare, configs): self.current_request_start = datetime.now() elif product_type == "AGENT_TASK": @@ -75,7 +76,8 @@ def _get_data_from_connector_and_exec(self): if self.current_request_start is None: log.warning("There is no tracer flare job to complete. Skipping new request.") continue + log.info("Generating and sending tracer flare") if _generate_tracer_flare(self.flare, configs): self.current_request_start = None else: - log.debug("Received unexpected product type for tracer flare: {}", product_type) + log.warning("Received unexpected product type for tracer flare: {}", product_type) diff --git a/ddtrace/internal/flare/flare.py b/ddtrace/internal/flare/flare.py index b37d6e8e013..5f9a582ae29 100644 --- a/ddtrace/internal/flare/flare.py +++ b/ddtrace/internal/flare/flare.py @@ -98,7 +98,6 @@ def send(self, flare_send_req: FlareSendRequest): before sending the flare. """ self.revert_configs() - # We only want the flare to be sent once, even if there are # multiple tracer instances lock_path = self.flare_dir / TRACER_FLARE_LOCK diff --git a/ddtrace/internal/flare/handler.py b/ddtrace/internal/flare/handler.py index 75ddac35188..efc5e872cb8 100644 --- a/ddtrace/internal/flare/handler.py +++ b/ddtrace/internal/flare/handler.py @@ -30,6 +30,7 @@ def __init__(self, callback: Callable, flare: Flare): def _handle_tracer_flare(flare: Flare, data: dict, cleanup: bool = False): if cleanup: + log.info("Reverting tracer flare configurations and cleaning up any generated files") flare.revert_configs() flare.clean_up_files() return @@ -51,7 +52,7 @@ def _handle_tracer_flare(flare: Flare, data: dict, cleanup: bool = False): log.warning("Received unexpected tracer flare product type: %s", product_type) -def _prepare_tracer_flare(flare: Flare, configs: List[dict]) -> bool: +def _prepare_tracer_flare(flare: Flare, configs: List[Any]) -> bool: """ Update configurations to start sending tracer logs to a file to be sent in a flare later. @@ -60,7 +61,13 @@ def _prepare_tracer_flare(flare: Flare, configs: List[dict]) -> bool: # AGENT_CONFIG is currently being used for multiple purposes # We only want to prepare for a tracer flare if the config name # starts with 'flare-log-level' + if not isinstance(c, dict): + log.debug("Config item is not type dict, received type %s instead. Skipping...", str(type(c))) + continue if not c.get("name", "").startswith("flare-log-level"): + log.debug( + "Config item name does not start with flare-log-level, received %s instead. Skipping...", c.get("name") + ) continue flare_log_level = c.get("config", {}).get("log_level").upper() @@ -78,7 +85,14 @@ def _generate_tracer_flare(flare: Flare, configs: List[Any]) -> bool: # AGENT_TASK is currently being used for multiple purposes # We only want to generate the tracer flare if the task_type is # 'tracer_flare' - if type(c) != dict or c.get("task_type") != "tracer_flare": + if not isinstance(c, dict): + log.debug("Config item is not type dict, received type %s instead. Skipping...", str(type(c))) + continue + if c.get("task_type") != "tracer_flare": + log.debug( + "Config item does not have the expected task_type. Expected [tracer_flare], received [%s]. Skipping...", + c.get("task_type"), + ) continue args = c.get("args", {}) flare_request = FlareSendRequest( diff --git a/releasenotes/notes/fix-tracer-flare-e4003d01b434267a.yaml b/releasenotes/notes/fix-tracer-flare-e4003d01b434267a.yaml new file mode 100644 index 00000000000..d3f8c7a3a99 --- /dev/null +++ b/releasenotes/notes/fix-tracer-flare-e4003d01b434267a.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + tracing(flare): Resolves the issue where tracer flares would not be generated if unexpected + types were received in the AGENT_CONFIG remote configuration product. diff --git a/tests/internal/test_tracer_flare.py b/tests/internal/test_tracer_flare.py index af8237c92e4..02a9684d3d5 100644 --- a/tests/internal/test_tracer_flare.py +++ b/tests/internal/test_tracer_flare.py @@ -234,7 +234,7 @@ def write(self): class TracerFlareSubscriberTests(TestCase): - agent_config = [{"name": "flare-log-level", "config": {"log_level": "DEBUG"}}] + agent_config = [False, {"name": "flare-log-level", "config": {"log_level": "DEBUG"}}] agent_task = [ False, { From fba9dbfec333fe09c13ac7197a0c19b32f5b5214 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Thu, 17 Oct 2024 14:04:27 -0400 Subject: [PATCH 015/372] feat(tracer): [SVLS-5676] DynamoDB TransactWriteItems pointers (#11060) This is similar but slightly different than BatchWriteItem. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../utils_botocore/span_pointers/dynamodb.py | 144 +++++++++++++++++- ...db-transactwriteitem-faf3ad03a100631d.yaml | 4 + .../utils_botocore/test_span_pointers.py | 93 ++++++++++- 3 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/span-pointers-aws-dynamodb-transactwriteitem-faf3ad03a100631d.yaml diff --git a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py index a1b13e62fc3..3b7d99cb0e4 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py @@ -52,6 +52,55 @@ class _DynamoDBDeleteRequestWriteRequest(TypedDict): _DynamoDBWriteRequest = Union[_DynamoDBPutRequestWriteRequest, _DynamoDBDeleteRequestWriteRequest] +class _DynamoDBTransactConditionCheck(TypedDict, total=False): + # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ConditionCheck.html + Key: _DynamoDBItemPrimaryKey + TableName: _DynamoDBTableName + + +class _DynamoDBTransactConditionCheckItem(TypedDict): + ConditionCheck: _DynamoDBTransactConditionCheck + + +class _DynanmoDBTransactDelete(TypedDict, total=False): + # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Delete.html + Key: _DynamoDBItemPrimaryKey + TableName: _DynamoDBTableName + + +class _DynamoDBTransactDeleteItem(TypedDict): + Delete: _DynanmoDBTransactDelete + + +class _DynamoDBTransactPut(TypedDict, total=False): + # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Put.html + Item: _DynamoDBItem + TableName: _DynamoDBTableName + + +class _DynamoDBTransactPutItem(TypedDict): + Put: _DynamoDBTransactPut + + +class _DynamoDBTransactUpdate(TypedDict, total=False): + # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Update.html + Key: _DynamoDBItemPrimaryKey + TableName: _DynamoDBTableName + + +class _DynamoDBTransactUpdateItem(TypedDict): + Update: _DynamoDBTransactUpdate + + +# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItem.html +_DynamoDBTransactWriteItem = Union[ + _DynamoDBTransactConditionCheckItem, + _DynamoDBTransactDeleteItem, + _DynamoDBTransactPutItem, + _DynamoDBTransactUpdateItem, +] + + def _extract_span_pointers_for_dynamodb_response( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], operation_name: str, @@ -63,20 +112,27 @@ def _extract_span_pointers_for_dynamodb_response( dynamodb_primary_key_names_for_tables, request_parameters ) - if operation_name in ("UpdateItem", "DeleteItem"): + elif operation_name in ("UpdateItem", "DeleteItem"): return _extract_span_pointers_for_dynamodb_keyed_operation_response( operation_name, request_parameters, ) - if operation_name == "BatchWriteItem": + elif operation_name == "BatchWriteItem": return _extract_span_pointers_for_dynamodb_batchwriteitem_response( dynamodb_primary_key_names_for_tables, request_parameters, response, ) - return [] + elif operation_name == "TransactWriteItems": + return _extract_span_pointers_for_dynamodb_transactwriteitems_response( + dynamodb_primary_key_names_for_tables, + request_parameters, + ) + + else: + return [] def _extract_span_pointers_for_dynamodb_putitem_response( @@ -165,6 +221,29 @@ def _extract_span_pointers_for_dynamodb_batchwriteitem_response( return [] +def _extract_span_pointers_for_dynamodb_transactwriteitems_response( + dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], + request_parameters: Dict[str, Any], +) -> List[_SpanPointerDescription]: + try: + return list( + itertools.chain.from_iterable( + _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( + dynamodb_primary_key_names_for_tables=dynamodb_primary_key_names_for_tables, + transact_write_request=transact_write_request, + ) + for transact_write_request in request_parameters["TransactItems"] + ) + ) + + except Exception as e: + log.warning( + "failed to generate DynamoDB.TransactWriteItems span pointer: %s", + str(e), + ) + return [] + + def _identify_dynamodb_batch_write_item_processed_items( requested_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], unprocessed_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], @@ -245,6 +324,65 @@ def _aws_dynamodb_item_primary_key_from_write_request( raise ValueError(f"unexpected write request structure: {''.join(sorted(write_request.keys()))}") +def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( + dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], + transact_write_request: _DynamoDBTransactWriteItem, +) -> List[_SpanPointerDescription]: + if len(transact_write_request) != 1: + raise ValueError(f"unexpected number of transact write request fields: {len(transact_write_request)}") + + if "ConditionCheck" in transact_write_request: + # ConditionCheck requests don't actually modify anything, so we don't + # consider the associated item to be passing information between spans. + return [] + + elif "Delete" in transact_write_request: + # Unfortunately mypy does not properly see the if statement above as a + # type-narrowing from _DynamoDBTransactWriteItem to + # _DynamoDBTransactDeleteItem, so we help it out ourselves. + + transact_write_request = cast(_DynamoDBTransactDeleteItem, transact_write_request) + + table_name = transact_write_request["Delete"]["TableName"] + key = transact_write_request["Delete"]["Key"] + + elif "Put" in transact_write_request: + # Unfortunately mypy does not properly see the if statement above as a + # type-narrowing from _DynamoDBTransactWriteItem to + # _DynamoDBTransactPutItem, so we help it out ourselves. + + transact_write_request = cast(_DynamoDBTransactPutItem, transact_write_request) + + table_name = transact_write_request["Put"]["TableName"] + key = _aws_dynamodb_item_primary_key_from_item( + dynamodb_primary_key_names_for_tables[table_name], + transact_write_request["Put"]["Item"], + ) + + elif "Update" in transact_write_request: + # Unfortunately mypy does not properly see the if statement above as a + # type-narrowing from _DynamoDBTransactWriteItem to + # _DynamoDBTransactUpdateItem, so we help it out ourselves. + + transact_write_request = cast(_DynamoDBTransactUpdateItem, transact_write_request) + + table_name = transact_write_request["Update"]["TableName"] + key = transact_write_request["Update"]["Key"] + + else: + raise ValueError( + f"unexpected transact write request structure: {''.join(sorted(transact_write_request.keys()))}" + ) + + return [ + _aws_dynamodb_item_span_pointer_description( + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=key, + ) + ] + + def _aws_dynamodb_item_span_pointer_description( pointer_direction: _SpanPointerDirection, table_name: _DynamoDBTableName, diff --git a/releasenotes/notes/span-pointers-aws-dynamodb-transactwriteitem-faf3ad03a100631d.yaml b/releasenotes/notes/span-pointers-aws-dynamodb-transactwriteitem-faf3ad03a100631d.yaml new file mode 100644 index 00000000000..356e4abb46c --- /dev/null +++ b/releasenotes/notes/span-pointers-aws-dynamodb-transactwriteitem-faf3ad03a100631d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + botocore: Adds span pointers for successful DynamoDB ``TransactWriteItems`` spans. Table Primary Keys will need to be provided with the ``ddtrace.config.botocore.dynamodb_primary_key_names_for_tables`` option or the ``DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS`` environment variable to correctly handle the ``Put`` items. diff --git a/tests/tracer/utils_botocore/test_span_pointers.py b/tests/tracer/utils_botocore/test_span_pointers.py index 1e5cd69170f..49209c4fc47 100644 --- a/tests/tracer/utils_botocore/test_span_pointers.py +++ b/tests/tracer/utils_botocore/test_span_pointers.py @@ -622,7 +622,7 @@ class PointersCase(NamedTuple): expected_warning_regex=".*'Key'.*", ), PointersCase( - name="BatchWriteItem works with multiple items and tables", + name="dynamodb.BatchWriteItem works with multiple items and tables", endpoint_name="dynamodb", operation_name="BatchWriteItem", request_parameters={ @@ -700,7 +700,7 @@ class PointersCase(NamedTuple): expected_warning_regex=None, ), PointersCase( - name="BatchWriteItem still needs the mapping sometimes", + name="dynamodb.BatchWriteItem still needs the mapping sometimes", endpoint_name="dynamodb", operation_name="BatchWriteItem", request_parameters={ @@ -720,6 +720,95 @@ class PointersCase(NamedTuple): expected_pointers=[], expected_warning_regex=".*unknown-table.*", ), + PointersCase( + name="dynamodb.TransactWriteItems basic case", + endpoint_name="dynamodb", + operation_name="TransactWriteItems", + request_parameters={ + "TransactItems": [ + { + "Put": { + "TableName": "some-table", + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + { + "Delete": { + "TableName": "unknown-table", + "Key": { + "some-key": {"S": "some-value"}, + }, + }, + }, + { + "Update": { + "TableName": "some-table", + "Key": { + "some-key": {"S": "some-value"}, + "other-key": {"N": "123"}, + }, + }, + }, + { + "ConditionCheck": { + "TableName": "do-not-care-table", + "Key": { + "do-not-care-key": {"S": "meh"}, + }, + }, + }, + ], + }, + response={ + # things we do not care about + }, + expected_pointers=[ + _SpanPointerDescription( + # Update + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7aa1b80b0e49bd2078a5453399f4dd67", + extra_attributes={}, + ), + _SpanPointerDescription( + # Put + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + _SpanPointerDescription( + # Delete + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="d8840182e4052ee105348b033e0a6810", + extra_attributes={}, + ), + ], + expected_warning_regex=None, + ), + PointersCase( + name="dynamodb.TransactWriteItems still needs the mapping sometimes", + endpoint_name="dynamodb", + operation_name="TransactWriteItems", + request_parameters={ + "TransactItems": [ + { + "Put": { + "TableName": "unknown-table", + "Item": { + "some-key": {"S": "some-value"}, + }, + }, + }, + ], + }, + response={}, + expected_pointers=[], + expected_warning_regex=".*unknown-table.*", + ), ], ids=lambda case: case.name, ) From ab8d89759bd371dfa377773f78e6ca1349d2225e Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:22:28 +0200 Subject: [PATCH 016/372] chore(core): core update for span management (#11012) Simplify the core "call_key" mechanism with a simple "span" attribute for the core context (based on a new _innerspan attribute). - `ctx.set_item(ctx.get_item("call_key"), span)` become `ctx.span = span` - `span = ctx.get_item(ctx["call_key"])` become `span = ctx.span` Add a "span_key" mechanism to automatically add the span to the context data for simple access in case we need to access a span by name on a non current context. * Technical Notes * I wanted to ensure that the inner span for a context is only set once. This is not the case, so I removed the condition. In the previous version, different spans could be assigned to the call_key during context life. As the logic was preserved in this PR, this is also the case with ctx.span. APPSEC-55053 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/trace_handlers.py | 61 +++++----- ddtrace/_trace/utils_redis.py | 4 +- ddtrace/appsec/_asm_request_context.py | 16 ++- ddtrace/contrib/internal/asgi/middleware.py | 2 +- ddtrace/contrib/internal/botocore/patch.py | 9 +- .../internal/botocore/services/bedrock.py | 3 +- .../internal/botocore/services/kinesis.py | 5 +- .../contrib/internal/botocore/services/sqs.py | 5 +- .../botocore/services/stepfunctions.py | 6 +- ddtrace/contrib/internal/django/patch.py | 12 +- ddtrace/contrib/internal/flask/patch.py | 8 +- ddtrace/contrib/internal/flask/wrappers.py | 3 +- ddtrace/contrib/internal/wsgi/wsgi.py | 6 +- ddtrace/contrib/rq/__init__.py | 16 +-- ddtrace/internal/core/__init__.py | 105 ++++++++++++------ tests/internal/test_context_events_api.py | 8 +- ...403_json[flask_appsec_good_rules_env].json | 6 +- ...json[flask_appsec_good_rules_env]_220.json | 6 +- 18 files changed, 149 insertions(+), 132 deletions(-) diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py index b4e3986baef..52c9210d067 100644 --- a/ddtrace/_trace/trace_handlers.py +++ b/ddtrace/_trace/trace_handlers.py @@ -28,7 +28,6 @@ from ddtrace.ext import http from ddtrace.internal import core from ddtrace.internal.compat import maybe_stringify -from ddtrace.internal.compat import nullcontext from ddtrace.internal.constants import COMPONENT from ddtrace.internal.constants import FLASK_ENDPOINT from ddtrace.internal.constants import FLASK_URL_RULE @@ -122,17 +121,16 @@ def _start_span(ctx: core.ExecutionContext, call_trace: bool = True, **kwargs) - span = (tracer.trace if call_trace else tracer.start_span)(ctx["span_name"], **span_kwargs) for tk, tv in ctx.get_item("tags", dict()).items(): span.set_tag_str(tk, tv) - ctx.set_item(ctx.get_item("call_key", "call"), span) + ctx.span = span return span def _on_traced_request_context_started_flask(ctx): current_span = ctx["pin"].tracer.current_span() if not ctx["pin"].enabled or not current_span: - ctx.set_item(ctx["call_key"], nullcontext()) return - ctx.set_item("call", current_span) + ctx.span = current_span flask_config = ctx["flask_config"] _set_flask_request_tags(ctx["flask_request"], current_span, flask_config) request_span = _start_span(ctx) @@ -315,7 +313,7 @@ def _cookies_from_response_headers(response_headers): def _on_flask_render(template, flask_config): - span = core.get_item("call") + span = core.get_span() if not span: return name = maybe_stringify(getattr(template, "name", None) or flask_config.get("template_default_name")) @@ -366,13 +364,13 @@ def _on_request_span_modifier_post(ctx, flask_config, request, req_body): def _on_traced_get_response_pre(_, ctx: core.ExecutionContext, request, before_request_tags): - before_request_tags(ctx["pin"], ctx["call"], request) - ctx["call"]._metrics[SPAN_MEASURED_KEY] = 1 + before_request_tags(ctx["pin"], ctx.span, request) + ctx.span._metrics[SPAN_MEASURED_KEY] = 1 def _on_django_finalize_response_pre(ctx, after_request_tags, request, response): # DEV: Always set these tags, this is where `span.resource` is set - span = ctx["call"] + span = ctx.span after_request_tags(ctx["pin"], span, request, response) trace_utils.set_http_meta(span, ctx["distributed_headers_config"], route=span.get_tag("http.route")) @@ -385,7 +383,7 @@ def _on_django_start_response( remake_body(request) trace_utils.set_http_meta( - ctx["call"], + ctx.span, ctx["distributed_headers_config"], method=request.method, query=query, @@ -398,24 +396,24 @@ def _on_django_start_response( def _on_django_cache(ctx: core.ExecutionContext, rowcount: int): - ctx["call"].set_metric(db.ROWCOUNT, rowcount) + ctx.span.set_metric(db.ROWCOUNT, rowcount) def _on_django_func_wrapped(_unused1, _unused2, _unused3, ctx, ignored_excs): if ignored_excs: for exc in ignored_excs: - ctx["call"]._ignore_exception(exc) + ctx.span._ignore_exception(exc) def _on_django_process_exception(ctx: core.ExecutionContext, should_set_traceback: bool): if should_set_traceback: - ctx["call"].set_traceback() + ctx.span.set_traceback() def _on_django_block_request(ctx: core.ExecutionContext, metadata: Dict[str, str], django_config, url: str, query: str): for tk, tv in metadata.items(): - ctx["call"].set_tag_str(tk, tv) - _set_url_tag(django_config, ctx["call"], url, query) + ctx.span.set_tag_str(tk, tv) + _set_url_tag(django_config, ctx.span, url, query) def _on_django_after_request_headers_post( @@ -449,7 +447,7 @@ def _on_django_after_request_headers_post( def _on_botocore_patched_api_call_started(ctx): - span = ctx.get_item(ctx.get_item("call_key")) + span = ctx.span set_patched_api_call_span_tags( span, ctx.get_item("instance"), @@ -467,7 +465,7 @@ def _on_botocore_patched_api_call_started(ctx): def _on_botocore_patched_api_call_exception(ctx, response, exception_type, is_error_code_fn): - span = ctx.get_item(ctx.get_item("call_key")) + span = ctx.span # `ClientError.response` contains the result, so we can still grab response metadata set_botocore_response_metadata_tags(span, response, is_error_code_fn=is_error_code_fn) @@ -479,7 +477,7 @@ def _on_botocore_patched_api_call_exception(ctx, response, exception_type, is_er def _on_botocore_patched_api_call_success(ctx, response): - span = ctx.get_item(ctx.get_item("call_key")) + span = ctx.span set_botocore_response_metadata_tags(span, response) @@ -498,7 +496,7 @@ def _on_botocore_trace_context_injection_prepared( ): endpoint_name = ctx.get_item("endpoint_name") if cloud_service is not None: - span = ctx.get_item(ctx["call_key"]) + span = ctx.span inject_kwargs = dict(endpoint_service=endpoint_name) if cloud_service == "sns" else dict() schematize_kwargs = dict(cloud_provider="aws", cloud_service=cloud_service) if endpoint_name != "lambda": @@ -514,22 +512,22 @@ def _on_botocore_kinesis_update_record(ctx, stream, data_obj: Dict, record, inje if inject_trace_context: if "_datadog" not in data_obj: data_obj["_datadog"] = {} - HTTPPropagator.inject(ctx[ctx["call_key"]].context, data_obj["_datadog"]) + HTTPPropagator.inject(ctx.span.context, data_obj["_datadog"]) def _on_botocore_update_messages(ctx, span, _, trace_data, __, message=None): - context = span.context if span else ctx[ctx["call_key"]].context + context = span.context if span else ctx.span.context HTTPPropagator.inject(context, trace_data) def _on_botocore_patched_stepfunctions_update_input(ctx, span, _, trace_data, __): - context = span.context if span else ctx[ctx["call_key"]].context + context = span.context if span else ctx.span.context HTTPPropagator.inject(context, trace_data["_datadog"]) ctx.set_item(BOTOCORE_STEPFUNCTIONS_INPUT_KEY, trace_data) def _on_botocore_patched_bedrock_api_call_started(ctx, request_params): - span = ctx[ctx["call_key"]] + span = ctx.span integration = ctx["bedrock_integration"] span.set_tag_str("bedrock.request.model_provider", ctx["model_provider"]) span.set_tag_str("bedrock.request.model", ctx["model_name"]) @@ -543,7 +541,7 @@ def _on_botocore_patched_bedrock_api_call_started(ctx, request_params): def _on_botocore_patched_bedrock_api_call_exception(ctx, exc_info): - span = ctx[ctx["call_key"]] + span = ctx.span span.set_exc_info(*exc_info) model_name = ctx["model_name"] integration = ctx["bedrock_integration"] @@ -553,7 +551,7 @@ def _on_botocore_patched_bedrock_api_call_exception(ctx, exc_info): def _on_botocore_patched_bedrock_api_call_success(ctx, reqid, latency, input_token_count, output_token_count): - span = ctx[ctx["call_key"]] + span = ctx.span span.set_tag_str("bedrock.response.id", reqid) span.set_tag_str("bedrock.response.duration", latency) span.set_tag_str("bedrock.usage.prompt_tokens", input_token_count) @@ -562,20 +560,15 @@ def _on_botocore_patched_bedrock_api_call_success(ctx, reqid, latency, input_tok def _propagate_context(ctx, headers): distributed_tracing_enabled = ctx["integration_config"].distributed_tracing_enabled - call_key = ctx.get_item("call_key") - if call_key is None: - log.warning("call_key not found in ctx") - if distributed_tracing_enabled and call_key: - span = ctx[ctx["call_key"]] + span = ctx.span + if distributed_tracing_enabled and span: HTTPPropagator.inject(span.context, headers) def _after_job_execution(ctx, job_failed, span_tags): """sets job.status and job.origin span tags after job is performed""" # get_status() returns None when ttl=0 - call_key = ctx.get_item("call_key") - if call_key: - span = ctx[ctx["call_key"]] + span = ctx.span if span: if job_failed: span.error = 1 @@ -598,7 +591,7 @@ def _on_botocore_bedrock_process_response( should_set_choice_ids: bool, ) -> None: text = formatted_response["text"] - span = ctx[ctx["call_key"]] + span = ctx.span model_name = ctx["model_name"] if should_set_choice_ids: for i in range(len(text)): @@ -651,7 +644,7 @@ def _on_botocore_kinesis_getrecords_post( def _on_redis_command_post(ctx: core.ExecutionContext, rowcount): if rowcount is not None: - ctx[ctx["call_key"]].set_metric(db.ROWCOUNT, rowcount) + ctx.span.set_metric(db.ROWCOUNT, rowcount) def _on_test_visibility_enable(config) -> None: diff --git a/ddtrace/_trace/utils_redis.py b/ddtrace/_trace/utils_redis.py index 9bc4bac3b8f..0ea1388e255 100644 --- a/ddtrace/_trace/utils_redis.py +++ b/ddtrace/_trace/utils_redis.py @@ -1,6 +1,7 @@ """ Some utils used by the dogtrace redis integration """ + from contextlib import contextmanager from typing import List from typing import Optional @@ -58,8 +59,7 @@ def _instrument_redis_cmd(pin, config_integration, instance, args): service=trace_utils.ext_service(pin, config_integration), span_type=SpanTypes.REDIS, resource=query.split(" ")[0] if config_integration.resource_only_command else query, - call_key="redis_command_call", - ) as ctx, ctx[ctx["call_key"]] as span: + ) as ctx, ctx.span as span: _set_span_tags(span, pin, config_integration, args, instance, query) yield ctx diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index ea282669f8e..7441a335ed5 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -58,13 +58,17 @@ class ASM_Environment: """ def __init__(self, span: Optional[Span] = None): + from ddtrace import tracer + self.root = not in_asm_context() if self.root: core.add_suppress_exception(BlockingException) - if span is None: - self.span: Span = core.get_item(core.get_item("call_key")) - else: - self.span = span + # add several layers of fallbacks to get a span, but normal span should be the first or the second one + context_span = span or core.get_span() or tracer.current_span() + if context_span is None: + log.debug("ASM context created without an available span") + context_span = tracer.trace("asm.context") + self.span: Span = context_span self.waf_addresses: Dict[str, Any] = {} self.callbacks: Dict[str, Any] = {_CONTEXT_CALL: []} self.telemetry: Dict[str, Any] = { @@ -506,9 +510,9 @@ def _on_set_request_tags(request, span, flask_config): def _on_pre_tracedrequest(ctx): - _on_set_request_tags(ctx.get_item("flask_request"), ctx["call"], ctx.get_item("flask_config")) + current_span = ctx.span + _on_set_request_tags(ctx.get_item("flask_request"), current_span, ctx.get_item("flask_config")) block_request_callable = ctx.get_item("block_request_callable") - current_span = ctx["call"] if asm_config._asm_enabled: set_block_request_callable(functools.partial(block_request_callable, current_span)) if get_blocked(): diff --git a/ddtrace/contrib/internal/asgi/middleware.py b/ddtrace/contrib/internal/asgi/middleware.py index 993f2500bd6..4a4b9eca9cb 100644 --- a/ddtrace/contrib/internal/asgi/middleware.py +++ b/ddtrace/contrib/internal/asgi/middleware.py @@ -151,7 +151,7 @@ async def __call__(self, scope, receive, send): span_type=SpanTypes.WEB, service=trace_utils.int_service(None, self.integration_config), pin=pin, - ) as ctx, ctx.get_item("call") as span: + ) as ctx, ctx.span as span: span.set_tag_str(COMPONENT, self.integration_config.integration_name) ctx.set_item("req_span", span) diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py index 6e7ecf23c62..ec5f0051c95 100644 --- a/ddtrace/contrib/internal/botocore/patch.py +++ b/ddtrace/contrib/internal/botocore/patch.py @@ -1,6 +1,7 @@ """ Trace queries to aws api done via botocore client """ + import collections import json import os @@ -149,7 +150,7 @@ def patched_lib_fn(original_func, instance, args, kwargs): "botocore.instrumented_lib_function", span_name="{}.{}".format(original_func.__module__, original_func.__name__), tags={COMPONENT: config.botocore.integration_name, SPAN_KIND: SpanKind.CLIENT}, - ) as ctx, ctx.get_item(ctx.get_item("call_key")): + ) as ctx, ctx.span: return original_func(*args, **kwargs) @@ -237,8 +238,8 @@ def patched_api_call_fallback(original_func, instance, args, kwargs, function_va pin=pin, span_name=function_vars.get("trace_operation"), span_type=SpanTypes.HTTP, - call_key="instrumented_api_call", - ) as ctx, ctx.get_item("instrumented_api_call"): + span_key="instrumented_api_call", + ) as ctx, ctx.span: core.dispatch("botocore.patched_api_call.started", [ctx]) if args and config.botocore["distributed_tracing"]: prep_context_injection(ctx, endpoint_name, operation, trace_operation, params) @@ -252,7 +253,7 @@ def patched_api_call_fallback(original_func, instance, args, kwargs, function_va ctx, e.response, botocore.exceptions.ClientError, - config.botocore.operations[ctx["instrumented_api_call"].resource].is_error_code, + config.botocore.operations[ctx.span.resource].is_error_code, ], ) raise diff --git a/ddtrace/contrib/internal/botocore/services/bedrock.py b/ddtrace/contrib/internal/botocore/services/bedrock.py index df547e8f095..dba11d1d450 100644 --- a/ddtrace/contrib/internal/botocore/services/bedrock.py +++ b/ddtrace/contrib/internal/botocore/services/bedrock.py @@ -287,7 +287,7 @@ def handle_bedrock_request(ctx: core.ExecutionContext) -> None: core.dispatch("botocore.patched_bedrock_api_call.started", [ctx, request_params]) prompt = None for k, v in request_params.items(): - if k == "prompt" and ctx["bedrock_integration"].is_pc_sampled_llmobs(ctx[ctx["call_key"]]): + if k == "prompt" and ctx["bedrock_integration"].is_pc_sampled_llmobs(ctx.span): prompt = v ctx.set_item("prompt", prompt) @@ -334,7 +334,6 @@ def patched_bedrock_api_call(original_func, instance, args, kwargs, function_var ), resource=function_vars.get("operation"), span_type=SpanTypes.LLM if submit_to_llmobs else None, - call_key="instrumented_bedrock_call", call_trace=True, bedrock_integration=integration, params=params, diff --git a/ddtrace/contrib/internal/botocore/services/kinesis.py b/ddtrace/contrib/internal/botocore/services/kinesis.py index 2d60252bdc2..1f8bbef1478 100644 --- a/ddtrace/contrib/internal/botocore/services/kinesis.py +++ b/ddtrace/contrib/internal/botocore/services/kinesis.py @@ -153,8 +153,7 @@ def _patched_kinesis_api_call(parent_ctx, original_func, instance, args, kwargs, activate=True, func_run=is_getrecords_call, start_ns=start_ns, - call_key="patched_kinesis_api_call", - ) as ctx, ctx.get_item(ctx.get_item("call_key")): + ) as ctx, ctx.span: core.dispatch("botocore.patched_kinesis_api_call.started", [ctx]) if is_kinesis_put_operation: @@ -181,7 +180,7 @@ def _patched_kinesis_api_call(parent_ctx, original_func, instance, args, kwargs, ctx, e.response, botocore.exceptions.ClientError, - config.botocore.operations[ctx[ctx["call_key"]].resource].is_error_code, + config.botocore.operations[ctx.span.resource].is_error_code, ], ) raise diff --git a/ddtrace/contrib/internal/botocore/services/sqs.py b/ddtrace/contrib/internal/botocore/services/sqs.py index da82df3113e..084a6f77dc7 100644 --- a/ddtrace/contrib/internal/botocore/services/sqs.py +++ b/ddtrace/contrib/internal/botocore/services/sqs.py @@ -155,9 +155,8 @@ def _patched_sqs_api_call(parent_ctx, original_func, instance, args, kwargs, fun endpoint_name=endpoint_name, operation=operation, call_trace=False, - call_key="instrumented_sqs_call", pin=pin, - ) as ctx, ctx.get_item(ctx.get_item("call_key")): + ) as ctx, ctx.span: core.dispatch("botocore.patched_sqs_api_call.started", [ctx]) if should_update_messages: @@ -181,7 +180,7 @@ def _patched_sqs_api_call(parent_ctx, original_func, instance, args, kwargs, fun ctx, e.response, botocore.exceptions.ClientError, - config.botocore.operations[ctx[ctx["call_key"]].resource].is_error_code, + config.botocore.operations[ctx.span.resource].is_error_code, ], ) raise diff --git a/ddtrace/contrib/internal/botocore/services/stepfunctions.py b/ddtrace/contrib/internal/botocore/services/stepfunctions.py index 7dac83c2a10..9e3c1d2db13 100644 --- a/ddtrace/contrib/internal/botocore/services/stepfunctions.py +++ b/ddtrace/contrib/internal/botocore/services/stepfunctions.py @@ -66,14 +66,14 @@ def patched_stepfunction_api_call(original_func, instance, args, kwargs: Dict, f span_name=call_name, service=schematize_service_name("{}.{}".format(ext_service(pin, int_config=config.botocore), endpoint_name)), span_type=SpanTypes.HTTP, - call_key="patched_stepfunctions_api_call", + span_key="patched_stepfunctions_api_call", instance=instance, args=args, params=params, endpoint_name=endpoint_name, operation=operation, pin=pin, - ) as ctx, ctx.get_item(ctx["call_key"]): + ) as ctx, ctx.span: core.dispatch("botocore.patched_stepfunctions_api_call.started", [ctx]) if should_update_input: @@ -88,7 +88,7 @@ def patched_stepfunction_api_call(original_func, instance, args, kwargs: Dict, f ctx, e.response, botocore.exceptions.ClientError, - config.botocore.operations[ctx[ctx["call_key"]].resource].is_error_code, + config.botocore.operations[ctx.span.resource].is_error_code, ], ) raise diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index f0835d50945..a22f11e4bda 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -216,7 +216,7 @@ def traced_cache(django, pin, func, instance, args, kwargs): resource=utils.resource_from_cache_prefix(func_name(func), instance), tags=tags, pin=pin, - ) as ctx, ctx["call"]: + ) as ctx, ctx.span: result = func(*args, **kwargs) rowcount = 0 if func.__name__ == "get_many": @@ -316,7 +316,7 @@ def wrapped(django, pin, func, instance, args, kwargs): tags = {COMPONENT: config.django.integration_name} with core.context_with_data( "django.func.wrapped", span_name=name, resource=resource, tags=tags, pin=pin - ) as ctx, ctx["call"]: + ) as ctx, ctx.span: core.dispatch( "django.func.wrapped", ( @@ -337,7 +337,7 @@ def wrapped(django, pin, func, instance, args, kwargs): tags = {COMPONENT: config.django.integration_name} with core.context_with_data( "django.process_exception", span_name=name, resource=resource, tags=tags, pin=pin - ) as ctx, ctx["call"]: + ) as ctx, ctx.span: resp = func(*args, **kwargs) core.dispatch( "django.process_exception", (ctx, hasattr(resp, "status_code") and 500 <= resp.status_code < 600) @@ -479,7 +479,7 @@ def traced_get_response(django, pin, func, instance, args, kwargs): distributed_headers_config=config.django, distributed_headers=request_headers, pin=pin, - ) as ctx, ctx.get_item("call"): + ) as ctx, ctx.span: core.dispatch( "django.traced_get_response.pre", ( @@ -509,7 +509,7 @@ def blocked_response(): response = HttpResponse(content, content_type=ctype, status=status) response.content = content response["Content-Length"] = len(content.encode()) - utils._after_request_tags(pin, ctx["call"], request, response) + utils._after_request_tags(pin, ctx.span, request, response) return response try: @@ -585,7 +585,7 @@ def traced_template_render(django, pin, wrapped, instance, args, kwargs): span_type=http.TEMPLATE, tags=tags, pin=pin, - ) as ctx, ctx["call"]: + ) as ctx, ctx.span: return wrapped(*args, **kwargs) diff --git a/ddtrace/contrib/internal/flask/patch.py b/ddtrace/contrib/internal/flask/patch.py index 393ec9cb91b..429a9d05667 100644 --- a/ddtrace/contrib/internal/flask/patch.py +++ b/ddtrace/contrib/internal/flask/patch.py @@ -479,7 +479,7 @@ def traced_render(wrapped, instance, args, kwargs): flask_config=config.flask, tags={COMPONENT: config.flask.integration_name}, span_type=SpanTypes.TEMPLATE, - ) as ctx, ctx.get_item("call"): + ) as ctx, ctx.span: return wrapped(*args, **kwargs) return traced_render @@ -531,9 +531,8 @@ def _patched_request(pin, wrapped, instance, args, kwargs): flask_request=flask.request, block_request_callable=_block_request_callable, ignored_exception_type=NotFound, - call_key="flask_request_call", tags={COMPONENT: config.flask.integration_name}, - ) as ctx, ctx.get_item("flask_request_call"): + ) as ctx, ctx.span: core.dispatch("flask._patched_request", (ctx,)) return wrapped(*args, **kwargs) @@ -564,6 +563,5 @@ def patched_jsonify(wrapped, instance, args, kwargs): flask_config=config.flask, tags={COMPONENT: config.flask.integration_name}, pin=pin, - call_key="flask_jsonify_call", - ) as ctx, ctx.get_item("flask_jsonify_call"): + ) as ctx, ctx.span: return wrapped(*args, **kwargs) diff --git a/ddtrace/contrib/internal/flask/wrappers.py b/ddtrace/contrib/internal/flask/wrappers.py index 61e300d4755..3aca2a1466a 100644 --- a/ddtrace/contrib/internal/flask/wrappers.py +++ b/ddtrace/contrib/internal/flask/wrappers.py @@ -43,8 +43,7 @@ def _wrap_call( service=trace_utils.int_service(pin, config.flask), span_type=span_type, tags=tags, - call_key="flask_call", - ) as ctx, ctx.get_item("flask_call"): + ) as ctx, ctx.span: if do_dispatch: result = core.dispatch_with_results("flask.wrapped_view", (kwargs,)).callback_and_args if result: diff --git a/ddtrace/contrib/internal/wsgi/wsgi.py b/ddtrace/contrib/internal/wsgi/wsgi.py index 4de10f5d503..da86aa8f21e 100644 --- a/ddtrace/contrib/internal/wsgi/wsgi.py +++ b/ddtrace/contrib/internal/wsgi/wsgi.py @@ -108,7 +108,7 @@ def __call__(self, environ: Iterable, start_response: Callable) -> wrapt.ObjectP distributed_headers=environ, environ=environ, middleware=self, - call_key="req_span", + span_key="req_span", ) as ctx: ctx.set_item("wsgi.construct_url", construct_url) @@ -187,7 +187,6 @@ def _traced_start_response(self, start_response, request_span, app_span, status, service=trace_utils.int_service(None, self._config), start_span=False, tags={COMPONENT: self._config.integration_name, SPAN_KIND: SpanKind.SERVER}, - call_key="response_span", ): return start_response(status, environ, exc_info) @@ -294,8 +293,7 @@ def _traced_start_response(self, start_response, request_span, app_span, status, service=trace_utils.int_service(None, self._config), start_span=True, tags={COMPONENT: self._config.integration_name, SPAN_KIND: SpanKind.SERVER}, - call_key="response_span", - ) as ctx, ctx.get_item("response_span"): + ) as ctx, ctx.span: return start_response(status, environ, exc_info) def _request_span_modifier(self, req_span, environ, parsed_headers=None): diff --git a/ddtrace/contrib/rq/__init__.py b/ddtrace/contrib/rq/__init__.py index 9369b2010bc..56ff26eb587 100644 --- a/ddtrace/contrib/rq/__init__.py +++ b/ddtrace/contrib/rq/__init__.py @@ -76,6 +76,7 @@ .. __: https://python-rq.org/ """ + import os from ddtrace import Pin @@ -148,7 +149,6 @@ def traced_queue_enqueue_job(rq, pin, func, instance, args, kwargs): service=trace_utils.int_service(pin, config.rq), resource=resource, span_type=SpanTypes.WORKER, - call_key="queue.enqueue_job", integration_config=config.rq_worker, tags={ COMPONENT: config.rq.integration_name, @@ -157,7 +157,7 @@ def traced_queue_enqueue_job(rq, pin, func, instance, args, kwargs): JOB_ID: job.get_id(), JOB_FUNC_NAME: job.func_name, }, - ) as ctx, ctx[ctx["call_key"]]: + ) as ctx, ctx.span: # If the queue is_async then add distributed tracing headers to the job if instance.is_async: core.dispatch("rq.queue.enqueue_job", [ctx, job.meta]) @@ -174,9 +174,8 @@ def traced_queue_fetch_job(rq, pin, func, instance, args, kwargs): ), pin=pin, service=trace_utils.int_service(pin, config.rq), - call_key="traced_queue_fetch_job", tags={COMPONENT: config.rq.integration_name, JOB_ID: job_id}, - ) as ctx, ctx[ctx["call_key"]]: + ) as ctx, ctx.span: return func(*args, **kwargs) @@ -194,11 +193,10 @@ def traced_perform_job(rq, pin, func, instance, args, kwargs): pin=pin, span_type=SpanTypes.WORKER, resource=job.func_name, - call_key="worker.perform_job", distributed_headers_config=config.rq_worker, distributed_headers=job.meta, tags={COMPONENT: config.rq.integration_name, SPAN_KIND: SpanKind.CONSUMER, JOB_ID: job.get_id()}, - ) as ctx, ctx[ctx["call_key"]]: + ) as ctx, ctx.span: try: return func(*args, **kwargs) finally: @@ -225,10 +223,9 @@ def traced_job_perform(rq, pin, func, instance, args, kwargs): "rq.job.perform", span_name="rq.job.perform", resource=job.func_name, - call_key="job.perform", pin=pin, tags={COMPONENT: config.rq.integration_name, JOB_ID: job.get_id()}, - ) as ctx, ctx[ctx["call_key"]]: + ) as ctx, ctx.span: return func(*args, **kwargs) @@ -242,10 +239,9 @@ def traced_job_fetch_many(rq, pin, func, instance, args, kwargs): "rq.job.fetch_many", provider="rq", direction=SpanDirection.PROCESSING ), service=trace_utils.ext_service(pin, config.rq_worker), - call_key="job.fetch_many", pin=pin, tags={COMPONENT: config.rq.integration_name, JOB_ID: job_ids}, - ) as ctx, ctx[ctx["call_key"]]: + ) as ctx, ctx.span: return func(*args, **kwargs) diff --git a/ddtrace/internal/core/__init__.py b/ddtrace/internal/core/__init__.py index 672634f2e0d..fab9b07c183 100644 --- a/ddtrace/internal/core/__init__.py +++ b/ddtrace/internal/core/__init__.py @@ -23,7 +23,7 @@ def _patched_request(pin, wrapped, args, kwargs): pin=pin, flask_request=flask.request, block_request_callable=_block_request_callable, - ) as ctx, ctx.get_item("flask_request_call"): + ) as ctx, ctx.span: return wrapped(*args, **kwargs) @@ -104,13 +104,13 @@ def _on_jsonify_context_started_flask(ctx): from contextlib import AbstractContextManager import logging import sys -from typing import TYPE_CHECKING # noqa:F401 +import types +import typing from typing import Any # noqa:F401 -from typing import Callable # noqa:F401 from typing import Dict # noqa:F401 from typing import List # noqa:F401 from typing import Optional # noqa:F401 -from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 from ddtrace.vendor.debtcollector import deprecate @@ -125,13 +125,10 @@ def _on_jsonify_context_started_flask(ctx): from .event_hub import reset as reset_listeners # noqa:F401 -if TYPE_CHECKING: +if typing.TYPE_CHECKING: from ddtrace._trace.span import Span # noqa:F401 -try: - import contextvars -except ImportError: - import ddtrace.vendor.contextvars as contextvars # type: ignore +import contextvars log = logging.getLogger(__name__) @@ -164,40 +161,46 @@ def _deprecate_span_kwarg(span): class ExecutionContext(AbstractContextManager): - def __init__(self, identifier, parent=None, span=None, **kwargs): + def __init__( + self, identifier: str, parent: Optional["ExecutionContext"] = None, span: Optional["Span"] = None, **kwargs + ) -> None: _deprecate_span_kwarg(span) - self.identifier = identifier - self._data = {} - self._parents = [] - self._span = span - self._suppress_exceptions = [] - if parent is not None: - self.addParent(parent) + self.identifier: str = identifier + self._data: Dict[str, Any] = {} + self._span: Optional["Span"] = span + self._suppress_exceptions: List[type] = [] self._data.update(kwargs) + self._parent: Optional["ExecutionContext"] = parent + self._inner_span: Optional["Span"] = None - def __enter__(self): + def __enter__(self) -> "ExecutionContext": if self._span is None and "_CURRENT_CONTEXT" in globals(): - self._token = _CURRENT_CONTEXT.set(self) + self._token: contextvars.Token["ExecutionContext"] = _CURRENT_CONTEXT.set(self) dispatch("context.started.%s" % self.identifier, (self,)) dispatch("context.started.start_span.%s" % self.identifier, (self,)) return self - def __repr__(self): - return self.__class__.__name__ + " '" + self.identifier + "' @ " + str(id(self)) + def __repr__(self) -> str: + return f"{self.__class__.__name__} '{self.identifier}' @ {id(self)}" @property - def parents(self): - return self._parents - - @property - def parent(self): - return self._parents[0] if self._parents else None - - def __exit__(self, exc_type, exc_value, traceback): + def parent(self) -> Optional["ExecutionContext"]: + return self._parent + + @parent.setter + def parent(self, value: "ExecutionContext") -> None: + if self._parent is not None: + raise ValueError("Cannot overwrite ExecutionContext parent") + self._parent = value + + def __exit__( + self, exc_type: Optional[type], exc_value: Optional[BaseException], traceback: Optional[types.TracebackType] + ) -> bool: dispatch("context.ended.%s" % self.identifier, (self,)) if self._span is None: try: - _CURRENT_CONTEXT.reset(self._token) + if hasattr(self, "_token"): + _CURRENT_CONTEXT.reset(self._token) except ValueError: log.debug( "Encountered ValueError during core contextvar reset() call. " @@ -217,13 +220,9 @@ def __exit__(self, exc_type, exc_value, traceback): else any(issubclass(exc_type, exc_type_) for exc_type_ in self._suppress_exceptions) ) - def addParent(self, context): - if self.identifier == ROOT_CONTEXT_ID: - raise ValueError("Cannot add parent to root context") - self._parents.append(context) - - def get_item(current, data_key: str, default: Optional[Any] = None) -> Any: + def get_item(self, data_key: str, default: Optional[Any] = None) -> Any: # NB mimic the behavior of `ddtrace.internal._context` by doing lazy inheritance + current: Optional[ExecutionContext] = self while current is not None: if data_key in current._data: return current._data.get(data_key) @@ -254,8 +253,9 @@ def set_items(self, keys_values: Dict[str, Optional[Any]]) -> None: for data_key, data_value in keys_values.items(): self.set_item(data_key, data_value) - def discard_item(current, data_key: str) -> None: + def discard_item(self, data_key: str) -> None: # NB mimic the behavior of `ddtrace.internal._context` by doing lazy inheritance + current: Optional[ExecutionContext] = self while current is not None: if data_key in current._data: del current._data[data_key] @@ -273,10 +273,24 @@ def root(self): current = current.parent return current + @property + def span(self) -> "Span": + if self._inner_span is None: + raise ValueError("No span set on ExecutionContext") + return self._inner_span + + @span.setter + def span(self, value: "Span") -> None: + self._inner_span = value + if "span_key" in self._data: + self._data[self._data["span_key"]] = value + def __getattr__(name): if name == "root": return _CURRENT_CONTEXT.get().root() + if name == "current": + return _CURRENT_CONTEXT.get() raise AttributeError @@ -343,3 +357,20 @@ def discard_item(data_key: str) -> None: def discard_local_item(data_key: str) -> None: _CURRENT_CONTEXT.get().discard_local_item(data_key) + + +def get_span() -> Optional["Span"]: + current: Optional[ExecutionContext] = _CURRENT_CONTEXT.get() + while current is not None: + try: + return current.span + except ValueError: + current = current.parent + return None + + +def get_root_span() -> Optional["Span"]: + span = _CURRENT_CONTEXT.get().span + if span is None: + return None + return span._local_root or span diff --git a/tests/internal/test_context_events_api.py b/tests/internal/test_context_events_api.py index 5491f54267f..814688da693 100644 --- a/tests/internal/test_context_events_api.py +++ b/tests/internal/test_context_events_api.py @@ -33,9 +33,9 @@ def tearDown(self): def test_core_get_execution_context(self): context = core.ExecutionContext("foo") - assert context.parents == [] - context.addParent(core.ExecutionContext("bar")) - assert len(context.parents) == 1 + assert context.parent is None + context.parent = core.ExecutionContext("bar") + assert context.parent is not None def test_core_has_listeners(self): event_name = "my.cool.event" @@ -332,7 +332,7 @@ def test_core_dispatch_context_ended(self): def test_core_root_context(self): root_context = core._CURRENT_CONTEXT.get() assert isinstance(root_context, core.ExecutionContext) - assert len(root_context.parents) == 0 + assert root_context.parent is None def test_core_current_context(self): assert core._CURRENT_CONTEXT.get().identifier == core.ROOT_CONTEXT_ID diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index 9da0021f5c9..f40ebda9c30 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -75,8 +75,6 @@ "flask.endpoint": "checkuser", "flask.url_rule": "/checkuser/", "flask.view_args.user_id": "123456", - "http.method": "GET", - "http.status_code": "403", "http.useragent": "python-requests/2.28.2" }, "duration": 13553833, @@ -131,7 +129,9 @@ "component": "flask", "error.message": "??? Unknown Error: None", "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/flask/patch.py\", line 531, in _traced_request\n return wrapped(*args, **kwargs)\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~10_itsdangerous210_markupsafe20_werkzeug20/lib/python3.9/site-packages/flask/app.py\", line 1936, in dispatch_request\n return self.view_functions[rule.endpoint](**req.view_args)\n File \"/root/project/ddtrace/contrib/flask/wrappers.py\", line 42, in trace_func\n return wrapped(*args, **kwargs)\n File \"/root/project/tests/contrib/flask/app.py\", line 68, in checkuser\n block_request_if_user_blocked(tracer, user_id)\n File \"/root/project/ddtrace/appsec/trace_utils.py\", line 252, in block_request_if_user_blocked\n _asm_request_context.block_request()\n File \"/root/project/ddtrace/appsec/_asm_request_context.py\", line 284, in block_request\n _callable()\n File \"/root/project/ddtrace/contrib/flask/patch.py\", line 504, in _block_request_callable\n abort(flask.Response(http_utils._get_blocked_template(ctype), content_type=ctype, status=403))\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~10_itsdangerous210_markupsafe20_werkzeug20/lib/python3.9/site-packages/werkzeug/exceptions.py\", line 822, in abort\n return _aborter(status, *args, **kwargs)\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~10_itsdangerous210_markupsafe20_werkzeug20/lib/python3.9/site-packages/werkzeug/exceptions.py\", line 804, in __call__\n raise HTTPException(response=code)\nwerkzeug.exceptions.HTTPException: ??? Unknown Error: None\n", - "error.type": "werkzeug.exceptions.HTTPException" + "error.type": "werkzeug.exceptions.HTTPException", + "http.method": "GET", + "http.status_code": "403" }, "duration": 12259875, "start": 1692710157868813170 diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index cce6a71fb93..5fa4782bf02 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -75,8 +75,6 @@ "flask.endpoint": "checkuser", "flask.url_rule": "/checkuser/", "flask.view_args.user_id": "123456", - "http.method": "GET", - "http.status_code": "403", "http.useragent": "python-requests/2.29.0" }, "duration": 7367750, @@ -114,7 +112,9 @@ "component": "flask", "error.message": "??? Unknown Error: None", "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/flask/patch.py\", line 531, in _traced_request\n return wrapped(*args, **kwargs)\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~22_importlib_metadata60/lib/python3.9/site-packages/flask/app.py\", line 1469, in dispatch_request\n return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)\n File \"/root/project/ddtrace/contrib/flask/wrappers.py\", line 42, in trace_func\n return wrapped(*args, **kwargs)\n File \"/root/project/tests/contrib/flask/app.py\", line 68, in checkuser\n block_request_if_user_blocked(tracer, user_id)\n File \"/root/project/ddtrace/appsec/trace_utils.py\", line 252, in block_request_if_user_blocked\n _asm_request_context.block_request()\n File \"/root/project/ddtrace/appsec/_asm_request_context.py\", line 284, in block_request\n _callable()\n File \"/root/project/ddtrace/contrib/flask/patch.py\", line 504, in _block_request_callable\n abort(flask.Response(http_utils._get_blocked_template(ctype), content_type=ctype, status=403))\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~22_importlib_metadata60/lib/python3.9/site-packages/werkzeug/exceptions.py\", line 876, in abort\n _aborter(status, *args, **kwargs)\n File \"/root/project/.riot/venv_py3916_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_blinker_requests_flask~22_importlib_metadata60/lib/python3.9/site-packages/werkzeug/exceptions.py\", line 856, in __call__\n raise HTTPException(response=code)\nwerkzeug.exceptions.HTTPException: ??? Unknown Error: None\n", - "error.type": "werkzeug.exceptions.HTTPException" + "error.type": "werkzeug.exceptions.HTTPException", + "http.method": "GET", + "http.status_code": "403" }, "duration": 6598042, "start": 1692722811998477594 From 3ea9300f405f5bfb86876f4c7d6955efdf62eb36 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Thu, 17 Oct 2024 14:43:43 -0400 Subject: [PATCH 017/372] feat(tracer): [SVLS-5765] optional botocore span pointers (#11068) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/trace_handlers.py | 17 +++++++++-------- ddtrace/contrib/botocore/__init__.py | 11 +++++++++++ ddtrace/contrib/internal/botocore/patch.py | 1 + ...ers-aws-opt-out-option-45f852835e642e98.yaml | 4 ++++ tests/contrib/botocore/test.py | 9 +++++++++ 5 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/span-pointers-aws-opt-out-option-45f852835e642e98.yaml diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py index 52c9210d067..1807ae220f6 100644 --- a/ddtrace/_trace/trace_handlers.py +++ b/ddtrace/_trace/trace_handlers.py @@ -481,14 +481,15 @@ def _on_botocore_patched_api_call_success(ctx, response): set_botocore_response_metadata_tags(span, response) - for span_pointer_description in extract_span_pointers_from_successful_botocore_response( - dynamodb_primary_key_names_for_tables=config.botocore.dynamodb_primary_key_names_for_tables, - endpoint_name=ctx.get_item("endpoint_name"), - operation_name=ctx.get_item("operation"), - request_parameters=ctx.get_item("params"), - response=response, - ): - _set_span_pointer(span, span_pointer_description) + if config.botocore.add_span_pointers: + for span_pointer_description in extract_span_pointers_from_successful_botocore_response( + dynamodb_primary_key_names_for_tables=config.botocore.dynamodb_primary_key_names_for_tables, + endpoint_name=ctx.get_item("endpoint_name"), + operation_name=ctx.get_item("operation"), + request_parameters=ctx.get_item("params"), + response=response, + ): + _set_span_pointer(span, span_pointer_description) def _on_botocore_trace_context_injection_prepared( diff --git a/ddtrace/contrib/botocore/__init__.py b/ddtrace/contrib/botocore/__init__.py index 11462e4ec78..143ef70b9d7 100644 --- a/ddtrace/contrib/botocore/__init__.py +++ b/ddtrace/contrib/botocore/__init__.py @@ -139,6 +139,17 @@ Default: ``{}`` + +.. py:data:: ddtrace.config.botocore['add_span_pointers'] + + This enables the addition of span pointers to spans associated with + successful AWS API calls. + + Alternatively, you can set this option with the + ``DD_BOTOCORE_ADD_SPAN_POINTERS`` environment variable. + + Default: ``True`` + """ diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py index ec5f0051c95..20de2d8cf11 100644 --- a/ddtrace/contrib/internal/botocore/patch.py +++ b/ddtrace/contrib/internal/botocore/patch.py @@ -103,6 +103,7 @@ def _load_dynamodb_primary_key_names_for_tables() -> Dict[str, Set[str]]: "propagation_enabled": asbool(os.getenv("DD_BOTOCORE_PROPAGATION_ENABLED", default=False)), "empty_poll_enabled": asbool(os.getenv("DD_BOTOCORE_EMPTY_POLL_ENABLED", default=True)), "dynamodb_primary_key_names_for_tables": _load_dynamodb_primary_key_names_for_tables(), + "add_span_pointers": asbool(os.getenv("DD_BOTOCORE_ADD_SPAN_POINTERS", default=True)), }, ) diff --git a/releasenotes/notes/span-pointers-aws-opt-out-option-45f852835e642e98.yaml b/releasenotes/notes/span-pointers-aws-opt-out-option-45f852835e642e98.yaml new file mode 100644 index 00000000000..e116cbe981e --- /dev/null +++ b/releasenotes/notes/span-pointers-aws-opt-out-option-45f852835e642e98.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + botocore: Add ``ddtrace.config.botocore.add_span_pointers`` option or the ``DD_BOTOCORE_ADD_SPAN_POINTERS`` environment variable to control adding span pointers to some successful AWS API requests. This option is enabled by default. diff --git a/tests/contrib/botocore/test.py b/tests/contrib/botocore/test.py index e51b99c90ca..657924be005 100644 --- a/tests/contrib/botocore/test.py +++ b/tests/contrib/botocore/test.py @@ -528,6 +528,15 @@ def test_s3_put(self): ), ] + @mock_s3 + def test_s3_put_with_add_span_pointers_false(self): + with self.override_config("botocore", dict(add_span_pointers=False)): + span = self._test_s3_put() + assert span.get_tag("aws.s3.bucket_name") == "mybucket" + assert span.get_tag("bucketname") == "mybucket" + + assert span._links == [] + @mock_s3 def test_s3_put_no_params(self): with self.override_config("botocore", dict(tag_no_params=True)): From f505953b07e7f635198e9d0cde5fea0662a3ad3d Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:06:43 -0400 Subject: [PATCH 018/372] chore: update changelog for version 2.11.7 (#11038) - [x] update changelog for version 2.11.7 --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f73eff200e..f68b037bb2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,23 @@ Changelogs for versions not listed here can be found at https://github.com/DataD --- -## 2.14.3 +## 2.11.7 + +### Bug Fixes + +- LLM Observability + - Fixes an issue where the OpenAI and LangChain integrations would still submit integration metrics even in agentless mode. Integration metrics are now disabled if using agentless mode via `LLMObs.enable(agentless_enabled=True)` or setting `DD_LLMOBS_AGENTLESS_ENABLED=1`. +- Code Security + - Resolves an issue where exploit prevention was not properly blocking requests with custom redirection actions. + - Resolves an issue where partial matches on function names we aimed to patch were being patched instead of full matches on them. + - Ensures the `Initializer` object is always reset and freed before the Python runtime. +- Profiling + - Improves the error message when the native exporter fails to load and stops profiling from starting if ddtrace is also being injected. + - Fixes endpoint profiling when using libdatadog exporter, either with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + +--- +## 2.14.3 ### Bug Fixes @@ -16,6 +31,8 @@ Changelogs for versions not listed here can be found at https://github.com/DataD - Tracing - Ensures `DD_TRACE_RATE_LIMIT` environment variable is only applied to spans for which tracer sampling is configured. For spans not matching sampling rules default rate limits should be applied by the Datadog Agent. +--- + ## 2.12.3 ### Bug Fixes From c2135e7411567c413078170cf840a45f948f8ead Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 17 Oct 2024 15:57:58 -0400 Subject: [PATCH 019/372] chore(telemetry): update config names and values (#10987) - Removes the name argument from `_ConfigItem.__init__(..)`. The name of a configuration will be set to the first environment variable in the envs field (ie the primary environment variable). - Avoids sending arbitrary configuration names to instrumentation telemetry. When possible, the configuration name should match a well documented name (ex: a public environment variable). - Avoids reporting integers, floats, booleans and None types as strings in instrumentation telemetry payloads ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/telemetry/writer.py | 6 ++- ddtrace/settings/config.py | 36 +++-------------- tests/integration/test_settings.py | 60 ++++++++++++++-------------- tests/telemetry/test_writer.py | 44 ++++++++++---------- 4 files changed, 63 insertions(+), 83 deletions(-) diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 4e8cecfb534..326e350e7b7 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -465,7 +465,11 @@ def remove_configuration(self, configuration_name): def add_configuration(self, configuration_name, configuration_value, origin="unknown"): # type: (str, Any, str) -> None """Creates and queues the name, origin, value of a configuration""" - if not isinstance(configuration_value, (bool, str, int, float, type(None))): + if isinstance(configuration_value, dict): + configuration_value = ",".join(":".join((k, str(v))) for k, v in configuration_value.items()) + elif isinstance(configuration_value, (list, tuple)): + configuration_value = ",".join(str(v) for v in configuration_value) + elif not isinstance(configuration_value, (bool, str, int, float, type(None))): # convert unsupported types to strings configuration_value = str(configuration_value) diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 2ce8f8d9415..178145601a1 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -198,10 +198,10 @@ def get_error_ranges(error_range_str): class _ConfigItem: """Configuration item that tracks the value of a setting, and where it came from.""" - def __init__(self, name, default, envs): - # type: (str, Union[_JSONType, Callable[[], _JSONType]], List[Tuple[str, Callable[[str], Any]]]) -> None + def __init__(self, default, envs): + # type: (Union[_JSONType, Callable[[], _JSONType]], List[Tuple[str, Callable[[str], Any]]]) -> None # _ConfigItem._name is only used in __repr__ and instrumentation telemetry - self._name = name + self._name = envs[0][0] self._env_value: _JSONType = None self._code_value: _JSONType = None self._rc_value: _JSONType = None @@ -214,7 +214,7 @@ def __init__(self, name, default, envs): if env_var in os.environ: self._env_value = parser(os.environ[env_var]) break - telemetry_writer.add_configuration(name, self._telemetry_value(), self.source()) + telemetry_writer.add_configuration(self._name, self.value(), self.source()) def set_value_source(self, value: Any, source: _ConfigSource) -> None: if source == "code": @@ -248,16 +248,6 @@ def source(self) -> _ConfigSource: return "env_var" return "default" - def _telemetry_value(self) -> str: - val = self.value() - if val is None: - return "" - elif isinstance(val, (bool, int, float)): - return str(val).lower() - elif isinstance(val, dict): - return ",".join(":".join((k, str(v))) for k, v in val.items()) - return str(val) - def __repr__(self): return "<{} name={} default={} env_value={} user_value={} remote_config_value={}>".format( self.__class__.__name__, @@ -277,52 +267,42 @@ def _parse_global_tags(s): def _default_config() -> Dict[str, _ConfigItem]: return { "_trace_sample_rate": _ConfigItem( - name="trace_sample_rate", default=1.0, envs=[("DD_TRACE_SAMPLE_RATE", float)], ), "_trace_sampling_rules": _ConfigItem( - name="trace_sampling_rules", default=lambda: "", envs=[("DD_TRACE_SAMPLING_RULES", str)], ), "_logs_injection": _ConfigItem( - name="logs_injection_enabled", default=False, envs=[("DD_LOGS_INJECTION", asbool)], ), "_trace_http_header_tags": _ConfigItem( - name="trace_header_tags", default=lambda: {}, envs=[("DD_TRACE_HEADER_TAGS", parse_tags_str)], ), "tags": _ConfigItem( - name="trace_tags", default=lambda: {}, envs=[("DD_TAGS", _parse_global_tags)], ), "_tracing_enabled": _ConfigItem( - name="trace_enabled", default=True, envs=[("DD_TRACE_ENABLED", asbool)], ), "_profiling_enabled": _ConfigItem( - name="profiling_enabled", default=False, envs=[("DD_PROFILING_ENABLED", asbool)], ), "_asm_enabled": _ConfigItem( - name="appsec_enabled", default=False, envs=[("DD_APPSEC_ENABLED", asbool)], ), "_sca_enabled": _ConfigItem( - name="DD_APPSEC_SCA_ENABLED", default=None, envs=[("DD_APPSEC_SCA_ENABLED", asbool)], ), "_dsm_enabled": _ConfigItem( - name="data_streams_enabled", default=False, envs=[("DD_DATA_STREAMS_ENABLED", asbool)], ), @@ -405,12 +385,6 @@ def __init__(self): # Must come before _integration_configs due to __setattr__ self._config = _default_config() - # Remove the SCA configuration from instrumentation telemetry if it is not set - # this behavior is validated by system tests - # FIXME(munir): Is this really needed? Should report the default value (None) instead? - if self._config["_sca_enabled"].value() is None: - telemetry_writer.remove_configuration("DD_APPSEC_SCA_ENABLED") - sample_rate = os.getenv("DD_TRACE_SAMPLE_RATE") if sample_rate is not None: deprecate( @@ -815,7 +789,7 @@ def _set_config_items(self, items): item = self._config[key] item.set_value_source(value, origin) if self._telemetry_enabled: - telemetry_writer.add_configuration(item._name, item._telemetry_value(), item.source()) + telemetry_writer.add_configuration(item._name, item.value(), item.source()) self._notify_subscribers(item_names) def _reset(self): diff --git a/tests/integration/test_settings.py b/tests/integration/test_settings.py index e71b14a3854..3b7e7d288b0 100644 --- a/tests/integration/test_settings.py +++ b/tests/integration/test_settings.py @@ -39,29 +39,29 @@ def test_setting_origin_environment(test_agent_session, run_python_code_in_subpr assert status == 0, err events = test_agent_session.get_events() - events_trace_sample_rate = _get_telemetry_config_items(events, "trace_sample_rate") + events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert { - "name": "trace_sample_rate", - "value": "0.1", + "name": "DD_TRACE_SAMPLE_RATE", + "value": 0.1, "origin": "env_var", } in events_trace_sample_rate - events_logs_injection_enabled = _get_telemetry_config_items(events, "logs_injection_enabled") - assert {"name": "logs_injection_enabled", "value": "true", "origin": "env_var"} in events_logs_injection_enabled + events_logs_injection_enabled = _get_telemetry_config_items(events, "DD_LOGS_INJECTION") + assert {"name": "DD_LOGS_INJECTION", "value": True, "origin": "env_var"} in events_logs_injection_enabled - events_trace_header_tags = _get_telemetry_config_items(events, "trace_header_tags") + events_trace_header_tags = _get_telemetry_config_items(events, "DD_TRACE_HEADER_TAGS") assert { - "name": "trace_header_tags", + "name": "DD_TRACE_HEADER_TAGS", "value": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2", "origin": "env_var", } in events_trace_header_tags - events_trace_tags = _get_telemetry_config_items(events, "trace_tags") - assert {"name": "trace_tags", "value": "team:apm,component:web", "origin": "env_var"} in events_trace_tags + events_trace_tags = _get_telemetry_config_items(events, "DD_TAGS") + assert {"name": "DD_TAGS", "value": "team:apm,component:web", "origin": "env_var"} in events_trace_tags - events_tracing_enabled = _get_telemetry_config_items(events, "trace_enabled") - assert {"name": "trace_enabled", "value": "true", "origin": "env_var"} in events_tracing_enabled + events_tracing_enabled = _get_telemetry_config_items(events, "DD_TRACE_ENABLED") + assert {"name": "DD_TRACE_ENABLED", "value": True, "origin": "env_var"} in events_tracing_enabled @pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent") @@ -96,38 +96,38 @@ def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess): assert status == 0, err events = test_agent_session.get_events() - events_trace_sample_rate = _get_telemetry_config_items(events, "trace_sample_rate") + events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert { - "name": "trace_sample_rate", - "value": "0.2", + "name": "DD_TRACE_SAMPLE_RATE", + "value": 0.2, "origin": "code", } in events_trace_sample_rate - events_logs_injection_enabled = _get_telemetry_config_items(events, "logs_injection_enabled") + events_logs_injection_enabled = _get_telemetry_config_items(events, "DD_LOGS_INJECTION") assert { - "name": "logs_injection_enabled", - "value": "false", + "name": "DD_LOGS_INJECTION", + "value": False, "origin": "code", } in events_logs_injection_enabled - events_trace_header_tags = _get_telemetry_config_items(events, "trace_header_tags") + events_trace_header_tags = _get_telemetry_config_items(events, "DD_TRACE_HEADER_TAGS") assert { - "name": "trace_header_tags", + "name": "DD_TRACE_HEADER_TAGS", "value": "header:value", "origin": "code", } in events_trace_header_tags - events_trace_tags = _get_telemetry_config_items(events, "trace_tags") + events_trace_tags = _get_telemetry_config_items(events, "DD_TAGS") assert { - "name": "trace_tags", + "name": "DD_TAGS", "value": "header:value", "origin": "code", } in events_trace_tags - events_tracing_enabled = _get_telemetry_config_items(events, "trace_enabled") + events_tracing_enabled = _get_telemetry_config_items(events, "DD_TRACE_ENABLED") assert { - "name": "trace_enabled", - "value": "false", + "name": "DD_TRACE_ENABLED", + "value": False, "origin": "code", } in events_tracing_enabled @@ -174,8 +174,8 @@ def test_remoteconfig_sampling_rate_default(test_agent_session, run_python_code_ assert status == 0, err events = test_agent_session.get_events() - events_trace_sample_rate = _get_telemetry_config_items(events, "trace_sample_rate") - assert {"name": "trace_sample_rate", "value": "1.0", "origin": "default"} in events_trace_sample_rate + events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") + assert {"name": "DD_TRACE_SAMPLE_RATE", "value": 1.0, "origin": "default"} in events_trace_sample_rate @pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent") @@ -201,8 +201,8 @@ def test_remoteconfig_sampling_rate_telemetry(test_agent_session, run_python_cod assert status == 0, err events = test_agent_session.get_events() - events_trace_sample_rate = _get_telemetry_config_items(events, "trace_sample_rate") - assert {"name": "trace_sample_rate", "value": "0.5", "origin": "remote_config"} in events_trace_sample_rate + events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") + assert {"name": "DD_TRACE_SAMPLE_RATE", "value": 0.5, "origin": "remote_config"} in events_trace_sample_rate @pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent") @@ -238,9 +238,9 @@ def test_remoteconfig_header_tags_telemetry(test_agent_session, run_python_code_ assert status == 0, err events = test_agent_session.get_events() - events_trace_header_tags = _get_telemetry_config_items(events, "trace_header_tags") + events_trace_header_tags = _get_telemetry_config_items(events, "DD_TRACE_HEADER_TAGS") assert { - "name": "trace_header_tags", + "name": "DD_TRACE_HEADER_TAGS", "value": "used:header_tag_69,unused:header_tag_70,used-with-default:", "origin": "remote_config", } in events_trace_header_tags diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index ef66017a078..92385ceb142 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -55,12 +55,12 @@ def test_add_event_disabled_writer(telemetry_writer, test_agent_session): @pytest.mark.parametrize( "env_var,value,expected_value", [ - ("DD_APPSEC_SCA_ENABLED", "true", "true"), - ("DD_APPSEC_SCA_ENABLED", "True", "true"), - ("DD_APPSEC_SCA_ENABLED", "1", "true"), - ("DD_APPSEC_SCA_ENABLED", "false", "false"), - ("DD_APPSEC_SCA_ENABLED", "False", "false"), - ("DD_APPSEC_SCA_ENABLED", "0", "false"), + ("DD_APPSEC_SCA_ENABLED", "true", True), + ("DD_APPSEC_SCA_ENABLED", "True", True), + ("DD_APPSEC_SCA_ENABLED", "1", True), + ("DD_APPSEC_SCA_ENABLED", "false", False), + ("DD_APPSEC_SCA_ENABLED", "False", False), + ("DD_APPSEC_SCA_ENABLED", "0", False), ], ) def test_app_started_event_configuration_override_asm( @@ -312,6 +312,11 @@ def test_app_started_event_configuration_override(test_agent_session, run_python }, {"name": "DD_APPSEC_RASP_ENABLED", "origin": "default", "value": True}, {"name": "DD_APPSEC_RULES", "origin": "default", "value": None}, + { + "name": "DD_APPSEC_SCA_ENABLED", + "origin": "default", + "value": None, + }, {"name": "DD_APPSEC_STACK_TRACE_ENABLED", "origin": "default", "value": True}, {"name": "DD_APPSEC_WAF_TIMEOUT", "origin": "default", "value": 5.0}, {"name": "DD_CIVISIBILITY_AGENTLESS_ENABLED", "origin": "env_var", "value": False}, @@ -325,7 +330,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_CRASHTRACKING_STACKTRACE_RESOLVER", "origin": "default", "value": "full"}, {"name": "DD_CRASHTRACKING_STDERR_FILENAME", "origin": "default", "value": None}, {"name": "DD_CRASHTRACKING_STDOUT_FILENAME", "origin": "default", "value": None}, - {"name": "DD_CRASHTRACKING_TAGS", "origin": "default", "value": "{}"}, + {"name": "DD_CRASHTRACKING_TAGS", "origin": "default", "value": ""}, {"name": "DD_CRASHTRACKING_WAIT_FOR_RECEIVER", "origin": "default", "value": True}, {"name": "DD_DATA_STREAMS_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_DOGSTATSD_PORT", "origin": "default", "value": None}, @@ -372,6 +377,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_LLMOBS_ENABLED", "origin": "default", "value": False}, {"name": "DD_LLMOBS_ML_APP", "origin": "default", "value": None}, {"name": "DD_LLMOBS_SAMPLE_RATE", "origin": "default", "value": 1.0}, + {"name": "DD_LOGS_INJECTION", "origin": "env_var", "value": True}, {"name": "DD_PROFILING_AGENTLESS", "origin": "default", "value": False}, {"name": "DD_PROFILING_API_TIMEOUT", "origin": "default", "value": 10.0}, {"name": "DD_PROFILING_CAPTURE_PCT", "origin": "env_var", "value": 5.0}, @@ -385,7 +391,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_PROFILING_MAX_TIME_USAGE_PCT", "origin": "default", "value": 1.0}, {"name": "DD_PROFILING_OUTPUT_PPROF", "origin": "default", "value": None}, {"name": "DD_PROFILING_SAMPLE_POOL_CAPACITY", "origin": "default", "value": 4}, - {"name": "DD_PROFILING_TAGS", "origin": "default", "value": "{}"}, + {"name": "DD_PROFILING_TAGS", "origin": "default", "value": ""}, {"name": "DD_PROFILING_TIMELINE_ENABLED", "origin": "default", "value": False}, {"name": "DD_PROFILING_UPLOAD_INTERVAL", "origin": "env_var", "value": 10.0}, {"name": "DD_PROFILING__FORCE_LEGACY_EXPORTER", "origin": "env_var", "value": True}, @@ -404,6 +410,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python }, {"name": "DD_SYMBOL_DATABASE_INCLUDES", "origin": "default", "value": "set()"}, {"name": "DD_SYMBOL_DATABASE_UPLOAD_ENABLED", "origin": "default", "value": False}, + {"name": "DD_TAGS", "origin": "env_var", "value": "team:apm,component:web"}, {"name": "DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED", "origin": "default", "value": True}, {"name": "DD_TELEMETRY_HEARTBEAT_INTERVAL", "origin": "default", "value": 60}, {"name": "DD_TESTING_RAISE", "origin": "env_var", "value": True}, @@ -417,6 +424,8 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_TRACE_CLIENT_IP_HEADER", "origin": "default", "value": None}, {"name": "DD_TRACE_COMPUTE_STATS", "origin": "env_var", "value": True}, {"name": "DD_TRACE_DEBUG", "origin": "env_var", "value": True}, + {"name": "DD_TRACE_ENABLED", "origin": "env_var", "value": False}, + {"name": "DD_TRACE_HEADER_TAGS", "origin": "default", "value": ""}, {"name": "DD_TRACE_HEALTH_METRICS_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "origin": "default", "value": "true"}, {"name": "DD_TRACE_HTTP_SERVER_ERROR_STATUSES", "origin": "default", "value": "500-599"}, @@ -431,6 +440,12 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_TRACE_PROPAGATION_STYLE_INJECT", "origin": "env_var", "value": "tracecontext"}, {"name": "DD_TRACE_RATE_LIMIT", "origin": "env_var", "value": 50}, {"name": "DD_TRACE_REPORT_HOSTNAME", "origin": "default", "value": False}, + {"name": "DD_TRACE_SAMPLE_RATE", "origin": "env_var", "value": 0.5}, + { + "name": "DD_TRACE_SAMPLING_RULES", + "origin": "env_var", + "value": '[{"sample_rate":1.0,"service":"xyz","name":"abc"}]', + }, {"name": "DD_TRACE_SPAN_AGGREGATOR_RLOCK", "origin": "default", "value": True}, {"name": "DD_TRACE_SPAN_TRACEBACK_MAX_SIZE", "origin": "default", "value": 30}, {"name": "DD_TRACE_STARTUP_LOGS", "origin": "env_var", "value": True}, @@ -447,24 +462,11 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "_DD_IAST_LAZY_TAINT", "origin": "default", "value": False}, {"name": "_DD_INJECT_WAS_ATTEMPTED", "origin": "default", "value": False}, {"name": "_DD_TRACE_WRITER_LOG_ERROR_PAYLOADS", "origin": "default", "value": False}, - {"name": "appsec_enabled", "origin": "env_var", "value": "true"}, - {"name": "data_streams_enabled", "origin": "env_var", "value": "true"}, {"name": "ddtrace_auto_used", "origin": "unknown", "value": True}, {"name": "ddtrace_bootstrapped", "origin": "unknown", "value": True}, - {"name": "logs_injection_enabled", "origin": "env_var", "value": "true"}, - {"name": "profiling_enabled", "origin": "env_var", "value": "true"}, {"name": "python_build_gnu_type", "origin": "unknown", "value": sysconfig.get_config_var("BUILD_GNU_TYPE")}, {"name": "python_host_gnu_type", "origin": "unknown", "value": sysconfig.get_config_var("HOST_GNU_TYPE")}, {"name": "python_soabi", "origin": "unknown", "value": sysconfig.get_config_var("SOABI")}, - {"name": "trace_enabled", "origin": "env_var", "value": "false"}, - {"name": "trace_header_tags", "origin": "default", "value": ""}, - {"name": "trace_sample_rate", "origin": "env_var", "value": "0.5"}, - { - "name": "trace_sampling_rules", - "origin": "env_var", - "value": '[{"sample_rate":1.0,"service":"xyz","name":"abc"}]', - }, - {"name": "trace_tags", "origin": "env_var", "value": "team:apm,component:web"}, ] assert configurations == expected, configurations From 22f25d4f90716ffba4a85484927bfa4eaa39001f Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 18 Oct 2024 09:39:45 +0200 Subject: [PATCH 020/372] chore(iast): separate iast and asm listens (#11053) Before this PR, IAST and ASM hooks were mixed together inside `asm_listen`. Now, all the IAST and ASM logic has been separated ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/__init__.py | 13 +- ddtrace/appsec/_handlers.py | 286 +------------ ddtrace/appsec/_iast/_handlers.py | 396 ++++++++++++++++++ ddtrace/appsec/_iast/_iast_request_context.py | 21 + ddtrace/appsec/_iast/_patch.py | 105 ----- ddtrace/appsec/_iast/processor.py | 8 +- ddtrace/contrib/internal/fastapi/patch.py | 2 +- tests/appsec/iast/conftest.py | 6 +- tests/appsec/iast/test_grpc_iast.py | 10 +- tests/appsec/iast/test_telemetry.py | 2 +- .../integrations/test_flask_telemetry.py | 2 +- .../fastapi/test_fastapi_appsec_iast.py | 2 +- 12 files changed, 443 insertions(+), 410 deletions(-) create mode 100644 ddtrace/appsec/_iast/_handlers.py diff --git a/ddtrace/appsec/__init__.py b/ddtrace/appsec/__init__.py index 6eb05ce2a9d..27463c7c8a1 100644 --- a/ddtrace/appsec/__init__.py +++ b/ddtrace/appsec/__init__.py @@ -1,13 +1,22 @@ _APPSEC_TO_BE_LOADED = True +_IAST_TO_BE_LOADED = True def load_appsec(): """Lazily load the appsec module listeners.""" from ddtrace.appsec._asm_request_context import asm_listen - from ddtrace.appsec._iast._iast_request_context import iast_listen global _APPSEC_TO_BE_LOADED if _APPSEC_TO_BE_LOADED: asm_listen() - iast_listen() _APPSEC_TO_BE_LOADED = False + + +def load_iast(): + """Lazily load the iast module listeners.""" + from ddtrace.appsec._iast._iast_request_context import iast_listen + + global _IAST_TO_BE_LOADED + if _IAST_TO_BE_LOADED: + iast_listen() + _IAST_TO_BE_LOADED = False diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index fde10f441de..4a3234ae7bd 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -1,18 +1,10 @@ -from collections.abc import MutableMapping -import functools import io import json -from wrapt import when_imported -from wrapt import wrap_function_wrapper as _w import xmltodict from ddtrace.appsec._asm_request_context import get_blocked from ddtrace.appsec._constants import SPAN_DATA_NAMES -from ddtrace.appsec._iast._iast_request_context import in_iast_context -from ddtrace.appsec._iast._patch import if_iast_taint_returned_object_for -from ddtrace.appsec._iast._patch import if_iast_taint_yield_tuple_for -from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.contrib import trace_utils from ddtrace.contrib.trace_utils import _get_request_header_user_agent from ddtrace.contrib.trace_utils import _set_url_tag @@ -27,13 +19,6 @@ from ddtrace.settings.asm import config as asm_config -MessageMapContainer = None -try: - from google._upb._message import MessageMapContainer # type: ignore[no-redef] -except ImportError: - pass - - log = get_logger(__name__) _BODY_METHODS = {"POST", "PUT", "DELETE", "PATCH"} @@ -69,12 +54,6 @@ def _on_set_http_meta( response_headers, response_cookies, ): - if _is_iast_enabled(): - from ddtrace.appsec._iast.taint_sinks.insecure_cookie import asm_check_cookies - - if response_cookies: - asm_check_cookies(response_cookies) - if asm_config._asm_enabled and span.span_type == SpanTypes.WEB: # avoid circular import from ddtrace.appsec._asm_request_context import set_waf_address @@ -193,270 +172,13 @@ def _on_request_span_modifier( return req_body -def _on_request_init(wrapped, instance, args, kwargs): - wrapped(*args, **kwargs) - if _is_iast_enabled() and in_iast_context(): - try: - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - - instance.query_string = taint_pyobject( - pyobject=instance.query_string, - source_name=origin_to_str(OriginType.QUERY), - source_value=instance.query_string, - source_origin=OriginType.QUERY, - ) - instance.path = taint_pyobject( - pyobject=instance.path, - source_name=origin_to_str(OriginType.PATH), - source_value=instance.path, - source_origin=OriginType.PATH, - ) - except Exception: - log.debug("Unexpected exception while tainting pyobject", exc_info=True) - - -def _on_flask_patch(flask_version): - if _is_iast_enabled(): - from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source - from ddtrace.appsec._iast._patch import _patched_dictionary - from ddtrace.appsec._iast._patch import try_wrap_function_wrapper - from ddtrace.appsec._iast._taint_tracking import OriginType - - try_wrap_function_wrapper( - "werkzeug.datastructures", - "Headers.items", - functools.partial(if_iast_taint_yield_tuple_for, (OriginType.HEADER_NAME, OriginType.HEADER)), - ) - _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) - _set_metric_iast_instrumented_source(OriginType.HEADER) - - try_wrap_function_wrapper( - "werkzeug.datastructures", - "ImmutableMultiDict.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), - ) - _set_metric_iast_instrumented_source(OriginType.PARAMETER) - - try_wrap_function_wrapper( - "werkzeug.datastructures", - "EnvironHeaders.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), - ) - _set_metric_iast_instrumented_source(OriginType.HEADER) - - if flask_version >= (2, 0, 0): - # instance.query_string: raising an error on werkzeug/_internal.py "AttributeError: read only property" - try_wrap_function_wrapper("werkzeug.wrappers.request", "Request.__init__", _on_request_init) - - _set_metric_iast_instrumented_source(OriginType.PATH) - _set_metric_iast_instrumented_source(OriginType.QUERY) - - # Instrumented on _ddtrace.appsec._asm_request_context._on_wrapped_view - _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) - - try_wrap_function_wrapper( - "werkzeug.wrappers.request", - "Request.get_data", - functools.partial(_patched_dictionary, OriginType.BODY, OriginType.BODY), - ) - try_wrap_function_wrapper( - "werkzeug.wrappers.request", - "Request.get_json", - functools.partial(_patched_dictionary, OriginType.BODY, OriginType.BODY), - ) - - _set_metric_iast_instrumented_source(OriginType.BODY) - - if flask_version < (2, 0, 0): - _w( - "werkzeug._internal", - "_DictAccessorProperty.__get__", - functools.partial(if_iast_taint_returned_object_for, OriginType.QUERY), - ) - _set_metric_iast_instrumented_source(OriginType.QUERY) - - -def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): - # If IAST is enabled and we're wrapping a Django view call, taint the kwargs (view's - # path parameters) - if _is_iast_enabled() and fn_args and isinstance(fn_args[0], first_arg_expected_type): - from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - from ddtrace.appsec._iast._taint_utils import taint_structure - - if not in_iast_context(): - return - - http_req = fn_args[0] - - http_req.COOKIES = taint_structure(http_req.COOKIES, OriginType.COOKIE_NAME, OriginType.COOKIE) - http_req.GET = taint_structure(http_req.GET, OriginType.PARAMETER_NAME, OriginType.PARAMETER) - http_req.POST = taint_structure(http_req.POST, OriginType.BODY, OriginType.BODY) - if ( - getattr(http_req, "_body", None) is not None - and len(getattr(http_req, "_body", None)) > 0 - and not is_pyobject_tainted(getattr(http_req, "_body", None)) - ): - try: - http_req._body = taint_pyobject( - http_req._body, - source_name=origin_to_str(OriginType.BODY), - source_value=http_req._body, - source_origin=OriginType.BODY, - ) - except AttributeError: - log.debug("IAST can't set attribute http_req._body", exc_info=True) - elif ( - getattr(http_req, "body", None) is not None - and len(getattr(http_req, "body", None)) > 0 - and not is_pyobject_tainted(getattr(http_req, "body", None)) - ): - try: - http_req.body = taint_pyobject( - http_req.body, - source_name=origin_to_str(OriginType.BODY), - source_value=http_req.body, - source_origin=OriginType.BODY, - ) - except AttributeError: - log.debug("IAST can't set attribute http_req.body", exc_info=True) - - http_req.headers = taint_structure(http_req.headers, OriginType.HEADER_NAME, OriginType.HEADER) - http_req.path = taint_pyobject( - http_req.path, source_name="path", source_value=http_req.path, source_origin=OriginType.PATH - ) - http_req.path_info = taint_pyobject( - http_req.path_info, - source_name=origin_to_str(OriginType.PATH), - source_value=http_req.path, - source_origin=OriginType.PATH, - ) - http_req.environ["PATH_INFO"] = taint_pyobject( - http_req.environ["PATH_INFO"], - source_name=origin_to_str(OriginType.PATH), - source_value=http_req.path, - source_origin=OriginType.PATH, - ) - http_req.META = taint_structure(http_req.META, OriginType.HEADER_NAME, OriginType.HEADER) - if fn_kwargs: - try: - for k, v in fn_kwargs.items(): - fn_kwargs[k] = taint_pyobject( - v, source_name=k, source_value=v, source_origin=OriginType.PATH_PARAMETER - ) - except Exception: - log.debug("IAST: Unexpected exception while tainting path parameters", exc_info=True) - - -def _on_wsgi_environ(wrapped, _instance, args, kwargs): - if _is_iast_enabled() and args and in_iast_context(): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_utils import taint_structure - - return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) - - return wrapped(*args, **kwargs) - - -def _on_django_patch(): - if _is_iast_enabled(): - try: - from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source - from ddtrace.appsec._iast._taint_tracking import OriginType - - # we instrument those sources on _on_django_func_wrapped - _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) - _set_metric_iast_instrumented_source(OriginType.HEADER) - _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) - _set_metric_iast_instrumented_source(OriginType.PATH) - _set_metric_iast_instrumented_source(OriginType.COOKIE) - _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) - _set_metric_iast_instrumented_source(OriginType.PARAMETER) - _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) - _set_metric_iast_instrumented_source(OriginType.BODY) - when_imported("django.http.request")( - lambda m: trace_utils.wrap( - m, - "QueryDict.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), - ) - ) - except Exception: - log.debug("Unexpected exception while patch IAST functions", exc_info=True) - - -def _custom_protobuf_getattribute(self, name): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - from ddtrace.appsec._iast._taint_utils import taint_structure - - ret = type(self).__saved_getattr(self, name) - if isinstance(ret, (str, bytes, bytearray)): - ret = taint_pyobject( - pyobject=ret, - source_name=OriginType.GRPC_BODY, - source_value=ret, - source_origin=OriginType.GRPC_BODY, - ) - elif MessageMapContainer is not None and isinstance(ret, MutableMapping): - if isinstance(ret, MessageMapContainer) and len(ret): - # Patch the message-values class - first_key = next(iter(ret)) - value_type = type(ret[first_key]) - _patch_protobuf_class(value_type) - else: - ret = taint_structure(ret, OriginType.GRPC_BODY, OriginType.GRPC_BODY) - - return ret - - -_custom_protobuf_getattribute.__datadog_custom = True # type: ignore[attr-defined] - - -# Used to replace the Protobuf message class "getattribute" with a custom one that taints the return -# of the original __getattribute__ method -def _patch_protobuf_class(cls): - getattr_method = getattr(cls, "__getattribute__") - if not getattr_method: - return - - if not hasattr(getattr_method, "__datadog_custom"): - try: - # Replace the class __getattribute__ method with our custom one - # (replacement is done at the class level because it would incur on a recursive loop with the instance) - cls.__saved_getattr = getattr_method - cls.__getattribute__ = _custom_protobuf_getattribute - except TypeError: - # Avoid failing on Python 3.12 while patching immutable types - pass - - -def _on_grpc_response(message): - if not _is_iast_enabled(): - return - - msg_cls = type(message) - _patch_protobuf_class(msg_cls) - - def _on_grpc_server_response(message): - if not _is_iast_enabled(): - return - from ddtrace.appsec._asm_request_context import set_waf_address set_waf_address(SPAN_DATA_NAMES.GRPC_SERVER_RESPONSE_MESSAGE, message) - _on_grpc_response(message) def _on_grpc_server_data(headers, request_message, method, metadata): - if not _is_iast_enabled(): - return - from ddtrace.appsec._asm_request_context import set_headers from ddtrace.appsec._asm_request_context import set_waf_address @@ -580,19 +302,13 @@ def _on_start_response_blocked(ctx, flask_config, response_headers, status): def listen(): core.on("set_http_meta_for_asm", _on_set_http_meta) core.on("flask.request_call_modifier", _on_request_span_modifier, "request_body") - core.on("flask.request_init", _on_request_init) + core.on("flask.blocked_request_callable", _on_flask_blocked_request) core.on("flask.start_response.blocked", _on_start_response_blocked) - core.on("django.func.wrapped", _on_django_func_wrapped) - core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") - core.on("django.patch", _on_django_patch) - core.on("flask.patch", _on_flask_patch) - core.on("asgi.request.parse.body", _on_asgi_request_parse_body, "await_receive_and_body") - core.on("grpc.client.response.message", _on_grpc_response) core.on("grpc.server.response.message", _on_grpc_server_response) core.on("grpc.server.data", _on_grpc_server_data) diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py new file mode 100644 index 00000000000..4ba0ecc86e0 --- /dev/null +++ b/ddtrace/appsec/_iast/_handlers.py @@ -0,0 +1,396 @@ +from collections.abc import MutableMapping +import functools + +from wrapt import when_imported +from wrapt import wrap_function_wrapper as _w + +from ddtrace.appsec._iast import _is_iast_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source +from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request +from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request_body +from ddtrace.appsec._iast._patch import _iast_instrument_starlette_url +from ddtrace.appsec._iast._patch import _patched_dictionary +from ddtrace.appsec._iast._patch import try_wrap_function_wrapper +from ddtrace.appsec._iast._taint_utils import taint_structure +from ddtrace.internal.logger import get_logger + + +MessageMapContainer = None +try: + from google._upb._message import MessageMapContainer # type: ignore[no-redef] +except ImportError: + pass + + +log = get_logger(__name__) + + +def _on_set_http_meta_iast( + span, + request_ip, + raw_uri, + route, + method, + request_headers, + request_cookies, + parsed_query, + request_path_params, + request_body, + status_code, + response_headers, + response_cookies, +): + if _is_iast_enabled(): + from ddtrace.appsec._iast.taint_sinks.insecure_cookie import asm_check_cookies + + if response_cookies: + asm_check_cookies(response_cookies) + + +def _on_request_init(wrapped, instance, args, kwargs): + from ddtrace.appsec._iast._iast_request_context import in_iast_context + + wrapped(*args, **kwargs) + if _is_iast_enabled() and in_iast_context(): + try: + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + instance.query_string = taint_pyobject( + pyobject=instance.query_string, + source_name=origin_to_str(OriginType.QUERY), + source_value=instance.query_string, + source_origin=OriginType.QUERY, + ) + instance.path = taint_pyobject( + pyobject=instance.path, + source_name=origin_to_str(OriginType.PATH), + source_value=instance.path, + source_origin=OriginType.PATH, + ) + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) + + +def _on_flask_patch(flask_version): + if _is_iast_enabled(): + from ddtrace.appsec._iast._taint_tracking import OriginType + + try_wrap_function_wrapper( + "werkzeug.datastructures", + "Headers.items", + functools.partial(if_iast_taint_yield_tuple_for, (OriginType.HEADER_NAME, OriginType.HEADER)), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) + _set_metric_iast_instrumented_source(OriginType.HEADER) + + try_wrap_function_wrapper( + "werkzeug.datastructures", + "ImmutableMultiDict.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + _set_metric_iast_instrumented_source(OriginType.PARAMETER) + + try_wrap_function_wrapper( + "werkzeug.datastructures", + "EnvironHeaders.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER) + + if flask_version >= (2, 0, 0): + # instance.query_string: raising an error on werkzeug/_internal.py "AttributeError: read only property" + try_wrap_function_wrapper("werkzeug.wrappers.request", "Request.__init__", _on_request_init) + + _set_metric_iast_instrumented_source(OriginType.PATH) + _set_metric_iast_instrumented_source(OriginType.QUERY) + + # Instrumented on _ddtrace.appsec._asm_request_context._on_wrapped_view + _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) + + try_wrap_function_wrapper( + "werkzeug.wrappers.request", + "Request.get_data", + functools.partial(_patched_dictionary, OriginType.BODY, OriginType.BODY), + ) + try_wrap_function_wrapper( + "werkzeug.wrappers.request", + "Request.get_json", + functools.partial(_patched_dictionary, OriginType.BODY, OriginType.BODY), + ) + + _set_metric_iast_instrumented_source(OriginType.BODY) + + if flask_version < (2, 0, 0): + _w( + "werkzeug._internal", + "_DictAccessorProperty.__get__", + functools.partial(if_iast_taint_returned_object_for, OriginType.QUERY), + ) + _set_metric_iast_instrumented_source(OriginType.QUERY) + + +def _on_wsgi_environ(wrapped, _instance, args, kwargs): + from ddtrace.appsec._iast._iast_request_context import in_iast_context + + if _is_iast_enabled() and args and in_iast_context(): + from ddtrace.appsec._iast._taint_tracking import OriginType + + return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) + + return wrapped(*args, **kwargs) + + +def _on_django_patch(): + if _is_iast_enabled(): + try: + from ddtrace.appsec._iast._taint_tracking import OriginType + + # we instrument those sources on _on_django_func_wrapped + _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) + _set_metric_iast_instrumented_source(OriginType.HEADER) + _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) + _set_metric_iast_instrumented_source(OriginType.PATH) + _set_metric_iast_instrumented_source(OriginType.COOKIE) + _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) + _set_metric_iast_instrumented_source(OriginType.PARAMETER) + _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) + _set_metric_iast_instrumented_source(OriginType.BODY) + when_imported("django.http.request")( + lambda m: try_wrap_function_wrapper( + m, + "QueryDict.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + ) + except Exception: + log.debug("Unexpected exception while patch IAST functions", exc_info=True) + + +def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): + # If IAST is enabled and we're wrapping a Django view call, taint the kwargs (view's + # path parameters) + if _is_iast_enabled() and fn_args and isinstance(fn_args[0], first_arg_expected_type): + from ddtrace.appsec._iast._iast_request_context import in_iast_context + from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + if not in_iast_context(): + return + + http_req = fn_args[0] + + http_req.COOKIES = taint_structure(http_req.COOKIES, OriginType.COOKIE_NAME, OriginType.COOKIE) + http_req.GET = taint_structure(http_req.GET, OriginType.PARAMETER_NAME, OriginType.PARAMETER) + http_req.POST = taint_structure(http_req.POST, OriginType.BODY, OriginType.BODY) + if ( + getattr(http_req, "_body", None) is not None + and len(getattr(http_req, "_body", None)) > 0 + and not is_pyobject_tainted(getattr(http_req, "_body", None)) + ): + try: + http_req._body = taint_pyobject( + http_req._body, + source_name=origin_to_str(OriginType.BODY), + source_value=http_req._body, + source_origin=OriginType.BODY, + ) + except AttributeError: + log.debug("IAST can't set attribute http_req._body", exc_info=True) + elif ( + getattr(http_req, "body", None) is not None + and len(getattr(http_req, "body", None)) > 0 + and not is_pyobject_tainted(getattr(http_req, "body", None)) + ): + try: + http_req.body = taint_pyobject( + http_req.body, + source_name=origin_to_str(OriginType.BODY), + source_value=http_req.body, + source_origin=OriginType.BODY, + ) + except AttributeError: + log.debug("IAST can't set attribute http_req.body", exc_info=True) + + http_req.headers = taint_structure(http_req.headers, OriginType.HEADER_NAME, OriginType.HEADER) + http_req.path = taint_pyobject( + http_req.path, source_name="path", source_value=http_req.path, source_origin=OriginType.PATH + ) + http_req.path_info = taint_pyobject( + http_req.path_info, + source_name=origin_to_str(OriginType.PATH), + source_value=http_req.path, + source_origin=OriginType.PATH, + ) + http_req.environ["PATH_INFO"] = taint_pyobject( + http_req.environ["PATH_INFO"], + source_name=origin_to_str(OriginType.PATH), + source_value=http_req.path, + source_origin=OriginType.PATH, + ) + http_req.META = taint_structure(http_req.META, OriginType.HEADER_NAME, OriginType.HEADER) + if fn_kwargs: + try: + for k, v in fn_kwargs.items(): + fn_kwargs[k] = taint_pyobject( + v, source_name=k, source_value=v, source_origin=OriginType.PATH_PARAMETER + ) + except Exception: + log.debug("IAST: Unexpected exception while tainting path parameters", exc_info=True) + + +def _custom_protobuf_getattribute(self, name): + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + ret = type(self).__saved_getattr(self, name) + if isinstance(ret, (str, bytes, bytearray)): + ret = taint_pyobject( + pyobject=ret, + source_name=OriginType.GRPC_BODY, + source_value=ret, + source_origin=OriginType.GRPC_BODY, + ) + elif MessageMapContainer is not None and isinstance(ret, MutableMapping): + if isinstance(ret, MessageMapContainer) and len(ret): + # Patch the message-values class + first_key = next(iter(ret)) + value_type = type(ret[first_key]) + _patch_protobuf_class(value_type) + else: + ret = taint_structure(ret, OriginType.GRPC_BODY, OriginType.GRPC_BODY) + + return ret + + +_custom_protobuf_getattribute.__datadog_custom = True # type: ignore[attr-defined] + + +# Used to replace the Protobuf message class "getattribute" with a custom one that taints the return +# of the original __getattribute__ method +def _patch_protobuf_class(cls): + getattr_method = getattr(cls, "__getattribute__") + if not getattr_method: + return + + if not hasattr(getattr_method, "__datadog_custom"): + try: + # Replace the class __getattribute__ method with our custom one + # (replacement is done at the class level because it would incur on a recursive loop with the instance) + cls.__saved_getattr = getattr_method + cls.__getattribute__ = _custom_protobuf_getattribute + except TypeError: + # Avoid failing on Python 3.12 while patching immutable types + pass + + +def _on_grpc_response(message): + if _is_iast_enabled(): + msg_cls = type(message) + _patch_protobuf_class(msg_cls) + + +def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): + if _is_iast_enabled(): + from ._iast_request_context import is_iast_request_enabled + from ._taint_tracking import taint_pyobject + + if not is_iast_request_enabled(): + for key, value in wrapped(*args, **kwargs): + yield key, value + else: + for key, value in wrapped(*args, **kwargs): + new_key = taint_pyobject(pyobject=key, source_name=key, source_value=key, source_origin=origins[0]) + new_value = taint_pyobject( + pyobject=value, source_name=key, source_value=value, source_origin=origins[1] + ) + yield new_key, new_value + + else: + for key, value in wrapped(*args, **kwargs): + yield key, value + + +def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): + value = wrapped(*args, **kwargs) + from ._iast_request_context import is_iast_request_enabled + + if _is_iast_enabled() and is_iast_request_enabled(): + try: + from ._taint_tracking import is_pyobject_tainted + from ._taint_tracking import taint_pyobject + + if not is_pyobject_tainted(value): + name = str(args[0]) if len(args) else "http.request.body" + from ddtrace.appsec._iast._taint_tracking import OriginType + + if origin == OriginType.HEADER and name.lower() in ["cookie", "cookies"]: + origin = OriginType.COOKIE + return taint_pyobject(pyobject=value, source_name=name, source_value=value, source_origin=origin) + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) + return value + + +def _on_iast_fastapi_patch(): + from ddtrace.appsec._iast._taint_tracking import OriginType + + # Cookies sources + try_wrap_function_wrapper( + "starlette.requests", + "cookie_parser", + functools.partial(_patched_dictionary, OriginType.COOKIE_NAME, OriginType.COOKIE), + ) + _set_metric_iast_instrumented_source(OriginType.COOKIE) + _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) + + # Parameter sources + try_wrap_function_wrapper( + "starlette.datastructures", + "QueryParams.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + try_wrap_function_wrapper( + "starlette.datastructures", + "QueryParams.get", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + _set_metric_iast_instrumented_source(OriginType.PARAMETER) + + # Header sources + try_wrap_function_wrapper( + "starlette.datastructures", + "Headers.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), + ) + try_wrap_function_wrapper( + "starlette.datastructures", + "Headers.get", + functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER) + + # Path source + try_wrap_function_wrapper("starlette.datastructures", "URL.__init__", _iast_instrument_starlette_url) + _set_metric_iast_instrumented_source(OriginType.PATH) + + # Body source + try_wrap_function_wrapper("starlette.requests", "Request.__init__", _iast_instrument_starlette_request) + try_wrap_function_wrapper("starlette.requests", "Request.body", _iast_instrument_starlette_request_body) + try_wrap_function_wrapper( + "starlette.datastructures", + "FormData.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), + ) + try_wrap_function_wrapper( + "starlette.datastructures", + "FormData.get", + functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), + ) + _set_metric_iast_instrumented_source(OriginType.BODY) + + # Instrumented on _iast_starlette_scope_taint + _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index 462a2975269..9a86ec83d64 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -6,6 +6,13 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import _is_iast_enabled from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._handlers import _on_django_func_wrapped +from ddtrace.appsec._iast._handlers import _on_django_patch +from ddtrace.appsec._iast._handlers import _on_flask_patch +from ddtrace.appsec._iast._handlers import _on_grpc_response +from ddtrace.appsec._iast._handlers import _on_request_init +from ddtrace.appsec._iast._handlers import _on_set_http_meta_iast +from ddtrace.appsec._iast._handlers import _on_wsgi_environ from ddtrace.appsec._iast._metrics import _set_metric_iast_request_tainted from ddtrace.appsec._iast._metrics import _set_span_tag_iast_executed_sink from ddtrace.appsec._iast._metrics import _set_span_tag_iast_request_tainted @@ -151,6 +158,20 @@ def _iast_start_request(span=None, *args, **kwargs): log.debug("[IAST] Error starting IAST context", exc_info=True) +def _on_grpc_server_response(message): + _on_grpc_response(message) + + def iast_listen(): + core.on("grpc.client.response.message", _on_grpc_response) + core.on("grpc.server.response.message", _on_grpc_server_response) + + core.on("set_http_meta_for_asm", _on_set_http_meta_iast) + core.on("django.patch", _on_django_patch) + core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") + core.on("django.func.wrapped", _on_django_func_wrapped) + core.on("flask.patch", _on_flask_patch) + core.on("flask.request_init", _on_request_init) + core.on("context.ended.wsgi.__call__", _iast_end_request) core.on("context.ended.asgi.__call__", _iast_end_request) diff --git a/ddtrace/appsec/_iast/_patch.py b/ddtrace/appsec/_iast/_patch.py index ea58bae9856..92d776c79cb 100644 --- a/ddtrace/appsec/_iast/_patch.py +++ b/ddtrace/appsec/_iast/_patch.py @@ -1,4 +1,3 @@ -import functools import sys from typing import Callable from typing import Text @@ -8,10 +7,7 @@ from ddtrace.appsec._common_module_patches import wrap_object from ddtrace.internal.logger import get_logger -from ._iast_request_context import is_iast_request_enabled -from ._metrics import _set_metric_iast_instrumented_source from ._taint_utils import taint_structure -from ._utils import _is_iast_enabled log = get_logger(__name__) @@ -45,113 +41,12 @@ def try_wrap_function_wrapper(module: Text, name: Text, wrapper: Callable): log.debug("IAST patching. Module %s.%s not exists", module, name) -def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): - value = wrapped(*args, **kwargs) - - if _is_iast_enabled() and is_iast_request_enabled(): - try: - from ._taint_tracking import is_pyobject_tainted - from ._taint_tracking import taint_pyobject - - if not is_pyobject_tainted(value): - name = str(args[0]) if len(args) else "http.request.body" - from ddtrace.appsec._iast._taint_tracking import OriginType - - if origin == OriginType.HEADER and name.lower() in ["cookie", "cookies"]: - origin = OriginType.COOKIE - return taint_pyobject(pyobject=value, source_name=name, source_value=value, source_origin=origin) - except Exception: - log.debug("Unexpected exception while tainting pyobject", exc_info=True) - return value - - -def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): - if _is_iast_enabled(): - from ._taint_tracking import taint_pyobject - - if not is_iast_request_enabled(): - for key, value in wrapped(*args, **kwargs): - yield key, value - else: - for key, value in wrapped(*args, **kwargs): - new_key = taint_pyobject(pyobject=key, source_name=key, source_value=key, source_origin=origins[0]) - new_value = taint_pyobject( - pyobject=value, source_name=key, source_value=value, source_origin=origins[1] - ) - yield new_key, new_value - - else: - for key, value in wrapped(*args, **kwargs): - yield key, value - - def _patched_dictionary(origin_key, origin_value, original_func, instance, args, kwargs): result = original_func(*args, **kwargs) return taint_structure(result, origin_key, origin_value, override_pyobject_tainted=True) -def _on_iast_fastapi_patch(): - from ddtrace.appsec._iast._taint_tracking import OriginType - - # Cookies sources - try_wrap_function_wrapper( - "starlette.requests", - "cookie_parser", - functools.partial(_patched_dictionary, OriginType.COOKIE_NAME, OriginType.COOKIE), - ) - _set_metric_iast_instrumented_source(OriginType.COOKIE) - _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) - - # Parameter sources - try_wrap_function_wrapper( - "starlette.datastructures", - "QueryParams.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), - ) - try_wrap_function_wrapper( - "starlette.datastructures", - "QueryParams.get", - functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), - ) - _set_metric_iast_instrumented_source(OriginType.PARAMETER) - - # Header sources - try_wrap_function_wrapper( - "starlette.datastructures", - "Headers.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), - ) - try_wrap_function_wrapper( - "starlette.datastructures", - "Headers.get", - functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), - ) - _set_metric_iast_instrumented_source(OriginType.HEADER) - - # Path source - try_wrap_function_wrapper("starlette.datastructures", "URL.__init__", _iast_instrument_starlette_url) - _set_metric_iast_instrumented_source(OriginType.PATH) - - # Body source - try_wrap_function_wrapper("starlette.requests", "Request.__init__", _iast_instrument_starlette_request) - try_wrap_function_wrapper("starlette.requests", "Request.body", _iast_instrument_starlette_request_body) - try_wrap_function_wrapper( - "starlette.datastructures", - "FormData.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), - ) - try_wrap_function_wrapper( - "starlette.datastructures", - "FormData.get", - functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), - ) - _set_metric_iast_instrumented_source(OriginType.BODY) - - # Instrumented on _iast_starlette_scope_taint - _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) - - def _iast_instrument_starlette_url(wrapped, instance, args, kwargs): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import origin_to_str diff --git a/ddtrace/appsec/_iast/processor.py b/ddtrace/appsec/_iast/processor.py index 2eb7d64243b..9a5e906faae 100644 --- a/ddtrace/appsec/_iast/processor.py +++ b/ddtrace/appsec/_iast/processor.py @@ -2,7 +2,6 @@ from ddtrace._trace.processor import SpanProcessor from ddtrace._trace.span import Span -from ddtrace.appsec import load_appsec from ddtrace.ext import SpanTypes from ddtrace.internal.logger import get_logger @@ -16,9 +15,9 @@ @dataclass(eq=False) class AppSecIastSpanProcessor(SpanProcessor): def __post_init__(self) -> None: - from ddtrace.appsec import load_appsec + from ddtrace.appsec import load_iast - load_appsec() + load_iast() def on_span_start(self, span: Span): if span.span_type != SpanTypes.WEB: @@ -37,6 +36,3 @@ def on_span_finish(self, span: Span): if span.span_type != SpanTypes.WEB: return _iast_end_request(span=span) - - -load_appsec() diff --git a/ddtrace/contrib/internal/fastapi/patch.py b/ddtrace/contrib/internal/fastapi/patch.py index 606ee066818..b431f3c83f8 100644 --- a/ddtrace/contrib/internal/fastapi/patch.py +++ b/ddtrace/contrib/internal/fastapi/patch.py @@ -87,7 +87,7 @@ def patch(): _w("starlette.routing", "Mount.handle", traced_handler) if _is_iast_enabled(): - from ddtrace.appsec._iast._patch import _on_iast_fastapi_patch + from ddtrace.appsec._iast._handlers import _on_iast_fastapi_patch _on_iast_fastapi_patch() diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 7184d15d15f..037754cb63a 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -53,7 +53,7 @@ def _end_iast_context_and_oce(span=None): oce.release_request() -def iast_context(env, request_sampling="100", deduplication=False): +def iast_context(env, request_sampling="100", deduplication=False, asm_enabled=False): try: from ddtrace.contrib.langchain.patch import patch as langchain_patch from ddtrace.contrib.langchain.patch import unpatch as langchain_unpatch @@ -78,7 +78,9 @@ class MockSpan: env.update({"DD_IAST_REQUEST_SAMPLING": request_sampling, "_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) VulnerabilityBase._reset_cache_for_testing() - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=deduplication)), override_env(env): + with override_global_config( + dict(_asm_enabled=asm_enabled, _iast_enabled=True, _deduplication_enabled=deduplication) + ), override_env(env): _start_iast_context_and_oce(MockSpan()) weak_hash_patch() weak_cipher_patch() diff --git a/tests/appsec/iast/test_grpc_iast.py b/tests/appsec/iast/test_grpc_iast.py index 1739d89aeda..47104e0915e 100644 --- a/tests/appsec/iast/test_grpc_iast.py +++ b/tests/appsec/iast/test_grpc_iast.py @@ -24,7 +24,7 @@ @pytest.fixture(autouse=True) def iast_c_context(): - yield from iast_context(dict(DD_IAST_ENABLED="true")) + yield from iast_context(dict(DD_IAST_ENABLED="true"), asm_enabled=True) def _check_test_range(value): @@ -151,8 +151,8 @@ def test_taint_iast_patching_import_error(self): with mock.patch.dict("sys.modules", {"google._upb._message": None}), override_env({"DD_IAST_ENABLED": "True"}): from collections import UserDict - from ddtrace.appsec._handlers import _custom_protobuf_getattribute - from ddtrace.appsec._handlers import _patch_protobuf_class + from ddtrace.appsec._iast._handlers import _custom_protobuf_getattribute + from ddtrace.appsec._iast._handlers import _patch_protobuf_class class MyUserDict(UserDict): pass @@ -164,9 +164,7 @@ class MyUserDict(UserDict): _custom_protobuf_getattribute(mutable_mapping, "data") def test_address_server_data(self): - with override_env({"DD_IAST_ENABLED": "True"}), override_global_config( - dict(_asm_enabled=True) - ), override_config("grpc", dict(service_name="myclientsvc")), override_config( + with override_config("grpc", dict(service_name="myclientsvc")), override_config( "grpc_server", dict(service_name="myserversvc") ): with mock.patch("ddtrace.appsec._asm_request_context.set_waf_address") as mock_set_waf_addr: diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 5ebe1b36fc4..a848e33cd4c 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -4,7 +4,7 @@ from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import IAST_SPAN_TAGS -from ddtrace.appsec._handlers import _on_django_patch +from ddtrace.appsec._iast._handlers import _on_django_patch from ddtrace.appsec._iast._metrics import TELEMETRY_DEBUG_VERBOSITY from ddtrace.appsec._iast._metrics import TELEMETRY_INFORMATION_VERBOSITY from ddtrace.appsec._iast._metrics import TELEMETRY_MANDATORY_VERBOSITY diff --git a/tests/appsec/integrations/test_flask_telemetry.py b/tests/appsec/integrations/test_flask_telemetry.py index fac88e443d7..6cea739b94f 100644 --- a/tests/appsec/integrations/test_flask_telemetry.py +++ b/tests/appsec/integrations/test_flask_telemetry.py @@ -1,4 +1,4 @@ -from ddtrace.appsec._handlers import _on_flask_patch +from ddtrace.appsec._iast._handlers import _on_flask_patch from tests.appsec.appsec_utils import flask_server from tests.utils import override_global_config diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 3f468ab51b4..df1abf8dbf1 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -16,7 +16,7 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce -from ddtrace.appsec._iast._patch import _on_iast_fastapi_patch +from ddtrace.appsec._iast._handlers import _on_iast_fastapi_patch from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli From b849a2a1197265a43a4960f49d302f4eccaa3470 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 18 Oct 2024 13:51:47 +0100 Subject: [PATCH 021/372] chore(di): better support for builtin collections (#10967) We improve the support for the serialisation of some builtin collection types when rendering template messages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_signal/utils.py | 31 ++++++++++++++---------------- tests/debugging/test_encoding.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/ddtrace/debugging/_signal/utils.py b/ddtrace/debugging/_signal/utils.py index ea24d1efc8f..b2e5d8e285b 100644 --- a/ddtrace/debugging/_signal/utils.py +++ b/ddtrace/debugging/_signal/utils.py @@ -1,3 +1,4 @@ +from collections import deque from itertools import islice from itertools import takewhile from types import FrameType @@ -73,19 +74,7 @@ def serialize( if not level: return repr(type(value)) - if type(value) not in BUILTIN_CONTAINER_TYPES: - return "%s(%s)" % ( - type(value).__name__, - ", ".join( - ( - "=".join((k, serialize(v, level - 1, maxsize, maxlen, maxfields))) - for k, v in islice(get_fields(value).items(), maxfields) - if not redact(k) - ) - ), - ) - - if type(value) is dict: + if type(value) in BUILTIN_MAPPING_TYPES: return "{%s}" % ", ".join( ( ": ".join( @@ -97,15 +86,23 @@ def serialize( for k, v in islice(value.items(), maxsize) ) ) - elif type(value) is list: + elif type(value) in {list, deque}: return _serialize_collection(value, "[]", level, maxsize, maxlen, maxfields) elif type(value) is tuple: return _serialize_collection(value, "()", level, maxsize, maxlen, maxfields) - elif type(value) is set: + elif type(value) in {set, frozenset}: return _serialize_collection(value, r"{}", level, maxsize, maxlen, maxfields) if value else "set()" - msg = f"Unhandled type: {type(value)}" - raise TypeError(msg) + return "%s(%s)" % ( + type(value).__name__, + ", ".join( + ( + "=".join((k, serialize(v, level - 1, maxsize, maxlen, maxfields))) + for k, v in islice(get_fields(value).items(), maxfields) + if not redact(k) + ) + ), + ) def capture_stack(top_frame: FrameType, max_height: int = 4096) -> List[dict]: diff --git a/tests/debugging/test_encoding.py b/tests/debugging/test_encoding.py index 258aecb582a..81239c639da 100644 --- a/tests/debugging/test_encoding.py +++ b/tests/debugging/test_encoding.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from collections import defaultdict import inspect import json import sys @@ -663,3 +664,14 @@ def test_capture_value_sequence_type(_type): ], "size": 1, } + + +@pytest.mark.parametrize( + "value,expected", + [ + (defaultdict(int, {"bar": 42}), "{'bar': 42}"), + (frozenset({"foo"}), "{'foo'}"), + ], +) +def test_serialize_builtins(value, expected): + assert utils.serialize(value) == expected From a201fc0680ffd3005e60345fc1c3cd8eb960e58c Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 18 Oct 2024 17:09:37 -0400 Subject: [PATCH 022/372] chore(ci): bump riot version for 3.7 compatibility issues (#11085) With the new release of virtualenv==20.27.0, we were seeing new failures with build_base_venv[3.7] (GitLab) and build_base_venv (CircleCI) jobs when running: ``` riot -P -v generate --python=$PYTHON_VERSION ... File "/go/src/github.com/DataDog/apm-reliability/dd-trace-py/.riot/venv_py3717/lib/python3.7/site-packages/pip/_vendor/typing_extensions.py", line 1039 def TypedDict(typename, fields=_marker, /, *, total=True, closed=False, **kwargs): ^ SyntaxError: invalid syntax ERROR:riot.riot:Dev install failed, aborting! ``` We need to pin to virtualenv==20.26.6 to continue supporting python3.7 The upload-artifact and download-artifact bumps were required to unblock the riot CI. It had been a while since the last update to riot, and since then, v2 has been deprecated and v3 will be deprecated next month (Nov 2024), so we are bumping to v4 via tag. The above changes were implemented in riot (https://github.com/DataDog/riot/pull/232), and this PR just bumps the riot version in dd-trace-py from 0.19.1 to 0.20.0 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: erikayasuda --- .circleci/config.templ.yml | 2 +- .circleci/config.yml | 2 +- .github/workflows/generate-package-versions.yml | 7 ++++--- .github/workflows/requirements-locks.yml | 2 +- .gitlab/package.yml | 2 +- .gitlab/tests.yml | 2 +- hatch.toml | 2 +- scripts/ddtest | 3 +-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 31aa71f1535..a3a57c1f6ab 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -80,7 +80,7 @@ commands: description: "Install riot" steps: # Make sure we install and run riot on Python 3 - - run: pip3 install riot==0.19.1 + - run: pip3 install riot==0.20.0 setup_rust: description: "Install rust toolchain" diff --git a/.circleci/config.yml b/.circleci/config.yml index cbac9570ebe..fcf74292ee3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: name: Generate config command: | export GIT_COMMIT_DESC=$(git log -n 1 $CIRCLE_SHA1) - pip3 install riot==0.19.1 + pip3 install riot==0.20.0 riot -P -v run --pass-env -s circleci-gen-config -- -v - continuation/continue: configuration_path: .circleci/config.gen.yml diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index 73bfce0fca0..b375ec95864 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -13,6 +13,7 @@ jobs: actions: read contents: write pull-requests: write + steps: - uses: actions/checkout@v4 @@ -45,7 +46,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.12" - + - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -76,7 +77,7 @@ jobs: python -m pip install --upgrade pip pip install packaging pip install requests - pip install riot==0.19.1 + pip install riot==0.20.0 - name: Run regenerate-riot-latest run: scripts/regenerate-riot-latest.sh @@ -98,4 +99,4 @@ jobs: base: main title: "chore: update ${{ env.VENV_NAME }} latest version to ${{ env.NEW_LATEST }}" labels: changelog/no-changelog - body-path: .github/PULL_REQUEST_TEMPLATE.md \ No newline at end of file + body-path: .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index 507ec17bbfc..b1703e9c27e 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -25,7 +25,7 @@ jobs: run: pyenv global 3.10 3.7 3.8 3.9 3.11 3.12 - name: Install Dependencies - run: pip install --upgrade pip && pip install riot + run: pip install --upgrade pip && pip install riot==0.20.0 - name: Generate riot locks run: scripts/compile-and-prune-test-requirements diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 625f9bdc641..115ada9f264 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -8,7 +8,7 @@ build_base_venvs: CMAKE_BUILD_PARALLEL_LEVEL: 12 PIP_VERBOSE: 1 script: - - pip install riot~=0.19.1 + - pip install riot==0.20.0 - riot -P -v generate --python=$PYTHON_VERSION artifacts: name: venv_$PYTHON_VERSION diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 85dc0cf5f44..8d1b3d16d54 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -40,7 +40,7 @@ variables: services: - !reference [.services, ddagent] script: - - pip install riot~=0.19.1 + - pip install riot==0.20.0 - unset DD_SERVICE - unset DD_ENV - unset DD_TAGS diff --git a/hatch.toml b/hatch.toml index ca74e784da6..f3cf8a21b9b 100644 --- a/hatch.toml +++ b/hatch.toml @@ -17,7 +17,7 @@ dependencies = [ "ddapm-test-agent>=1.2.0", "packaging==23.1", "pygments==2.16.1", - "riot==0.19.1", + "riot==0.20.0", "ruff==0.1.3", "clang-format==18.1.5", "cmake-format==0.6.13", diff --git a/scripts/ddtest b/scripts/ddtest index ea08231a861..d4bc6e90a0c 100755 --- a/scripts/ddtest +++ b/scripts/ddtest @@ -19,8 +19,7 @@ fi for i in {1..3}; do $compose_cmd pull -q testrunner && break || sleep 3; done # TODO(DEV): Install riot in the docker image -FULL_CMD="pip install -q --disable-pip-version-check riot==0.19.1 && $CMD" - +FULL_CMD="pip install -q --disable-pip-version-check riot==0.20.0 && $CMD" # install and upgrade riot in case testrunner image has not been updated # DEV: Use `--no-TTY` and `--quiet-pull` when running in CircleCI From f4056158acb4b2aa91c1b71d3af56fd293fe3abb Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 18 Oct 2024 18:23:18 -0400 Subject: [PATCH 023/372] chore(iast): propagate python executable (#11084) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7c8c90c6f1a..93bb92a2fec 100644 --- a/setup.py +++ b/setup.py @@ -343,6 +343,7 @@ def build_extension_cmake(self, ext): "-S{}".format(ext.source_dir), # cmake>=3.13 "-B{}".format(cmake_build_dir), # cmake>=3.13 "-DPython3_ROOT_DIR={}".format(sysconfig.get_config_var("prefix")), + "-DPYTHON_EXECUTABLE={}".format(sys.executable), "-DCMAKE_BUILD_TYPE={}".format(ext.build_type), "-DLIB_INSTALL_DIR={}".format(output_dir), "-DEXTENSION_NAME={}".format(extension_basename), From e2f1c3681493149f6a191b634e50c9542fa49bb1 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 21 Oct 2024 09:42:15 +0200 Subject: [PATCH 024/372] chore(iast): report iast enabled metric based on environment variable (#11079) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_iast_request_context.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index 9a86ec83d64..eab39e0449d 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -111,13 +111,14 @@ def is_iast_request_enabled(): def _iast_end_request(ctx=None, span=None, *args, **kwargs): try: + if span: + req_span = span + else: + req_span = ctx.get_item("req_span") + if _is_iast_enabled(): - if span: - req_span = span - else: - req_span = ctx.get_item("req_span") exist_data = req_span.get_tag(IAST.JSON) - if not exist_data: + if exist_data is None and req_span.get_metric(IAST.ENABLED) is None: if not is_iast_request_enabled(): req_span.set_metric(IAST.ENABLED, 0.0) end_iast_context(req_span) @@ -142,6 +143,7 @@ def _iast_end_request(ctx=None, span=None, *args, **kwargs): req_span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) oce.release_request() + except Exception: log.debug("[IAST] Error finishing IAST context", exc_info=True) From fdbb6503824d9330478dc9e52dffbc528219bac2 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 21 Oct 2024 09:42:31 +0200 Subject: [PATCH 025/372] feat(iast): add support for cookie vulnerability for FastAPI (#11080) This PR adds support for detecting cookies that are not marked as secure, are not using the HttpOnly mark, or are not using one of the secure options of the Samesite= setting ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .../_iast/taint_sinks/insecure_cookie.py | 68 +++--- ddtrace/contrib/internal/asgi/middleware.py | 13 +- .../fastapi/test_fastapi_appsec_iast.py | 202 ++++++++++++++++++ 3 files changed, 254 insertions(+), 29 deletions(-) diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index f5539e919fb..f4cb00fc433 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -2,9 +2,12 @@ from typing import Optional from ..._constants import IAST_SPAN_TAGS +from .. import _is_iast_enabled from .. import oce +from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_metric_iast_executed_sink from .._metrics import increment_iast_span_metric +from .._taint_tracking import iast_taint_log_error from ..constants import VULN_INSECURE_COOKIE from ..constants import VULN_NO_HTTPONLY_COOKIE from ..constants import VULN_NO_SAMESITE_COOKIE @@ -33,35 +36,44 @@ class NoSameSite(VulnerabilityBase): def asm_check_cookies(cookies: Optional[Dict[str, str]]) -> None: if not cookies: return + if _is_iast_enabled() and is_iast_request_enabled(): + try: + for cookie_key, cookie_value in cookies.items(): + lvalue = cookie_value.lower().replace(" ", "") + # If lvalue starts with ";" means that the cookie is empty, like ';httponly;path=/;samesite=strict' + if lvalue == "" or lvalue.startswith(";") or lvalue.startswith('""'): + continue - for cookie_key, cookie_value in cookies.items(): - lvalue = cookie_value.lower().replace(" ", "") - # If lvalue starts with ";" means that the cookie is empty, like ';httponly;path=/;samesite=strict' - if lvalue == "" or lvalue.startswith(";"): - continue + if ";secure" not in lvalue: + increment_iast_span_metric( + IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, InsecureCookie.vulnerability_type + ) + _set_metric_iast_executed_sink(InsecureCookie.vulnerability_type) + InsecureCookie.report(evidence_value=cookie_key) - if ";secure" not in lvalue: - increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, InsecureCookie.vulnerability_type) - _set_metric_iast_executed_sink(InsecureCookie.vulnerability_type) - InsecureCookie.report(evidence_value=cookie_key) + if ";httponly" not in lvalue: + increment_iast_span_metric( + IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoHttpOnlyCookie.vulnerability_type + ) + _set_metric_iast_executed_sink(NoHttpOnlyCookie.vulnerability_type) + NoHttpOnlyCookie.report(evidence_value=cookie_key) - if ";httponly" not in lvalue: - increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoHttpOnlyCookie.vulnerability_type) - _set_metric_iast_executed_sink(NoHttpOnlyCookie.vulnerability_type) - NoHttpOnlyCookie.report(evidence_value=cookie_key) + if ";samesite=" in lvalue: + ss_tokens = lvalue.split(";samesite=") + if len(ss_tokens) <= 1: + report_samesite = True + else: + ss_tokens[1] = ss_tokens[1].lower() + if ss_tokens[1].startswith("strict") or ss_tokens[1].startswith("lax"): + report_samesite = False + else: + report_samesite = True + else: + report_samesite = True - if ";samesite=" in lvalue: - ss_tokens = lvalue.split(";samesite=") - if len(ss_tokens) == 0: - report_samesite = True - elif ss_tokens[1].startswith("strict") or ss_tokens[1].startswith("lax"): - report_samesite = False - else: - report_samesite = True - else: - report_samesite = True - - if report_samesite: - increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoSameSite.vulnerability_type) - _set_metric_iast_executed_sink(NoSameSite.vulnerability_type) - NoSameSite.report(evidence_value=cookie_key) + if report_samesite: + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoSameSite.vulnerability_type) + _set_metric_iast_executed_sink(NoSameSite.vulnerability_type) + NoSameSite.report(evidence_value=cookie_key) + except Exception as e: + iast_taint_log_error("[IAST] error in asm_check_cookies. {}".format(e)) diff --git a/ddtrace/contrib/internal/asgi/middleware.py b/ddtrace/contrib/internal/asgi/middleware.py index 4a4b9eca9cb..ac2f61ecf26 100644 --- a/ddtrace/contrib/internal/asgi/middleware.py +++ b/ddtrace/contrib/internal/asgi/middleware.py @@ -236,9 +236,20 @@ async def wrapped_send(message): response_headers = None if span and message.get("type") == "http.response.start" and "status" in message: + cookies = {} + try: + cookie_key, cookie_value = response_headers.get("set-cookie", "").split("=", maxsplit=1) + cookies[cookie_key] = cookie_value + except Exception: + log.debug("failed to extract response cookies", exc_info=True) + status_code = message["status"] trace_utils.set_http_meta( - span, self.integration_config, status_code=status_code, response_headers=response_headers + span, + self.integration_config, + status_code=status_code, + response_headers=response_headers, + response_cookies=cookies, ) core.dispatch("asgi.start_response", ("asgi",)) core.dispatch("asgi.finalize_response", (message.get("body"), response_headers)) diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index df1abf8dbf1..0c8f538c743 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -17,6 +17,9 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._handlers import _on_iast_fastapi_patch +from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE +from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE +from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli @@ -569,3 +572,202 @@ async def test_route(param_str): assert vulnerability["location"]["line"] == line assert vulnerability["location"]["path"] == TEST_FILE_PATH assert vulnerability["hash"] == hash_value + + +def test_fasapi_insecure_cookie(fastapi_application, client, tracer, test_spans): + @fastapi_application.route("/insecure_cookie/", methods=["GET"]) + def insecure_cookie(request: Request): + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import origin_to_str + + query_params = request.query_params.get("iast_queryparam") + ranges_result = get_tainted_ranges(query_params) + response = JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + } + ) + response.set_cookie(key="insecure", value=query_params, secure=False, httponly=True, samesite="strict") + + return response + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/insecure_cookie/?iast_queryparam=insecure", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_INSECURE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + +def test_fasapi_insecure_cookie_empty(fastapi_application, client, tracer, test_spans): + @fastapi_application.route("/insecure_cookie/", methods=["GET"]) + def insecure_cookie(request: Request): + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import origin_to_str + + query_params = request.query_params.get("iast_queryparam") + ranges_result = get_tainted_ranges(query_params) + response = JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + } + ) + response.set_cookie(key="insecure", value="", secure=False, httponly=True, samesite="strict") + + return response + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/insecure_cookie/?iast_queryparam=insecure", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + loaded = span.get_tag(IAST.JSON) + assert loaded is None + + +def test_fasapi_no_http_only_cookie(fastapi_application, client, tracer, test_spans): + @fastapi_application.route("/insecure_cookie/", methods=["GET"]) + def insecure_cookie(request: Request): + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import origin_to_str + + query_params = request.query_params.get("iast_queryparam") + ranges_result = get_tainted_ranges(query_params) + response = JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + } + ) + response.set_cookie(key="insecure", value=query_params, secure=True, httponly=False, samesite="strict") + + return response + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/insecure_cookie/?iast_queryparam=insecure", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_NO_HTTPONLY_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + +def test_fasapi_no_http_only_cookie_empty(fastapi_application, client, tracer, test_spans): + @fastapi_application.route("/insecure_cookie/", methods=["GET"]) + def insecure_cookie(request: Request): + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import origin_to_str + + query_params = request.query_params.get("iast_queryparam") + ranges_result = get_tainted_ranges(query_params) + response = JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + } + ) + response.set_cookie(key="insecure", value="", secure=True, httponly=False, samesite="strict") + + return response + + with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/insecure_cookie/?iast_queryparam=insecure", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + loaded = span.get_tag(IAST.JSON) + assert loaded is None + + +def test_fasapi_no_samesite_cookie(fastapi_application, client, tracer, test_spans): + @fastapi_application.route("/insecure_cookie/", methods=["GET"]) + def insecure_cookie(request: Request): + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import origin_to_str + + query_params = request.query_params.get("iast_queryparam") + ranges_result = get_tainted_ranges(query_params) + response = JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + } + ) + response.set_cookie(key="insecure", value=query_params, secure=True, httponly=True, samesite="none") + + return response + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/insecure_cookie/?iast_queryparam=insecure", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_NO_SAMESITE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] From 911ae003303d1666d342136e24fbe1968826e7e3 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 21 Oct 2024 10:08:03 +0200 Subject: [PATCH 026/372] chore(iast): add new private environment variable (#11045) This PR introduces a new private environment variable for IAST. - _DD_IAST_PROPAGATION_DEBUG: print the propagation frames for each function that uses a tainted parameter - _DD_IAST_DEBUG: send the stacktrace in telemetry log errors ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_constants.py | 4 +- .../appsec/_iast/_overhead_control_engine.py | 3 +- .../appsec/_iast/_taint_tracking/__init__.py | 3 +- ddtrace/appsec/_iast/_utils.py | 41 ++----------------- ddtrace/settings/asm.py | 9 +++- tests/appsec/appsec/test_constants.py | 1 - tests/appsec/appsec_utils.py | 8 ++-- tests/appsec/iast/_ast/conftest.py | 4 +- .../iast/_ast/test_ast_patching_type_hints.py | 28 ++++++++++++- tests/appsec/iast/aspects/conftest.py | 4 +- tests/appsec/iast/aspects/test_add_aspect.py | 7 ++-- .../aspects/test_bytearray_extend_aspect.py | 5 +-- .../aspects/test_format_aspect_fixtures.py | 5 +-- .../aspects/test_index_aspect_fixtures.py | 5 +-- .../iast/aspects/test_join_aspect_fixtures.py | 9 ++-- .../aspects/test_ospath_aspects_fixtures.py | 5 +-- .../aspects/test_slice_aspect_fixtures.py | 5 +-- .../appsec/iast/aspects/test_split_aspect.py | 5 +-- tests/appsec/iast/conftest.py | 15 ++++--- tests/appsec/iast/taint_tracking/conftest.py | 4 +- .../taint_tracking/test_native_taint_range.py | 5 +-- .../taint_tracking/test_taint_tracking.py | 4 +- .../iast/test_overhead_control_engine.py | 9 ++-- tests/appsec/iast/test_processor.py | 13 ++++-- tests/appsec/iast/test_telemetry.py | 15 +++---- .../test_aggregated_memleaks.py | 4 +- tests/appsec/iast_packages/test_packages.py | 3 +- tests/appsec/integrations/test_psycopg2.py | 4 +- tests/contrib/dbapi/test_dbapi_appsec.py | 14 ++----- .../contrib/django/test_django_appsec_iast.py | 26 ++++++------ .../fastapi/test_fastapi_appsec_iast.py | 33 +++++++-------- tests/contrib/flask/test_flask_appsec_iast.py | 41 +++++++------------ tests/telemetry/test_writer.py | 1 + 33 files changed, 158 insertions(+), 184 deletions(-) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 5b48dfaa181..f7f9f3b12fc 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -114,7 +114,9 @@ class IAST(metaclass=Constant_Class): """Specific constants for IAST""" ENV: Literal["DD_IAST_ENABLED"] = "DD_IAST_ENABLED" - ENV_DEBUG: Literal["_DD_IAST_DEBUG"] = "_DD_IAST_DEBUG" + ENV_DEBUG: Literal["DD_IAST_DEBUG"] = "DD_IAST_DEBUG" + ENV_PROPAGATION_DEBUG: Literal["DD_IAST_PROPAGATION_DEBUG"] = "DD_IAST_PROPAGATION_DEBUG" + ENV_REQUEST_SAMPLING: Literal["DD_IAST_REQUEST_SAMPLING"] = "DD_IAST_REQUEST_SAMPLING" TELEMETRY_REPORT_LVL: Literal["DD_IAST_TELEMETRY_VERBOSITY"] = "DD_IAST_TELEMETRY_VERBOSITY" LAZY_TAINT: Literal["_DD_IAST_LAZY_TAINT"] = "_DD_IAST_LAZY_TAINT" JSON: Literal["_dd.iast.json"] = "_dd.iast.json" diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index e9180c53b3e..036e4d3cbfd 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -14,6 +14,7 @@ from ddtrace.internal._unpatched import _threading as threading from ddtrace.internal.logger import get_logger from ddtrace.sampler import RateSampler +from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) @@ -21,7 +22,7 @@ def get_request_sampling_value() -> float: # Percentage of requests analyzed by IAST (default: 30%) - return float(os.environ.get("DD_IAST_REQUEST_SAMPLING", 30.0)) + return float(asm_config._iast_request_sampling) MAX_REQUESTS = int(os.environ.get("DD_IAST_MAX_CONCURRENT_REQUESTS", 2)) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 1e2e61b4bc3..323d227c0ec 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -13,6 +13,7 @@ from .._metrics import _set_iast_error_metric from .._metrics import _set_metric_iast_executed_source from .._utils import _is_iast_debug_enabled +from .._utils import _is_iast_propagation_debug_enabled from .._utils import _is_python_version_supported @@ -211,7 +212,7 @@ def get_tainted_ranges(pyobject: Any) -> Tuple: return tuple() -if _is_iast_debug_enabled(): +if _is_iast_propagation_debug_enabled(): TAINTED_FRAMES = [] def trace_calls_and_returns(frame, event, arg): diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index 9c66a562e9a..c1ae2d82be4 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -1,12 +1,8 @@ from functools import lru_cache -import os import sys from typing import List -from typing import Text -from ddtrace.appsec._constants import IAST from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.formats import asbool from ddtrace.settings.asm import config as asm_config @@ -37,38 +33,9 @@ def _get_source_index(sources: List, source) -> int: return -1 -def _get_patched_code(module_path: Text, module_name: Text) -> str: - """ - Print the patched code to stdout, for debugging purposes. - """ - import astunparse - - from ddtrace.appsec._iast._ast.ast_patching import get_encoding - from ddtrace.appsec._iast._ast.ast_patching import visit_ast - - with open(module_path, "r", encoding=get_encoding(module_path)) as source_file: - source_text = source_file.read() - - new_source = visit_ast( - source_text, - module_path, - module_name=module_name, - ) - - # If no modifications are done, - # visit_ast returns None - if not new_source: - return "" - - new_code = astunparse.unparse(new_source) - return new_code - - -if __name__ == "__main__": - MODULE_PATH = sys.argv[1] - MODULE_NAME = sys.argv[2] - print(_get_patched_code(MODULE_PATH, MODULE_NAME)) +def _is_iast_debug_enabled(): + return asm_config._iast_debug -def _is_iast_debug_enabled(): - return asbool(os.environ.get(IAST.ENV_DEBUG, "false")) +def _is_iast_propagation_debug_enabled(): + return asm_config._iast_propagation_debug diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index b871c595a33..0764b157d97 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -15,7 +15,6 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import LOGIN_EVENTS_MODE from ddtrace.constants import APPSEC_ENV -from ddtrace.constants import IAST_ENV from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.settings._core import report_telemetry as _report_telemetry from ddtrace.vendor.debtcollector import deprecate @@ -65,7 +64,10 @@ class ASMConfig(Env): # prevent empty string if _asm_static_rule_file == "": _asm_static_rule_file = None - _iast_enabled = Env.var(bool, IAST_ENV, default=False) + _iast_enabled = Env.var(bool, IAST.ENV, default=False) + _iast_request_sampling = Env.var(float, IAST.ENV_REQUEST_SAMPLING, default=30.0) + _iast_debug = Env.var(bool, IAST.ENV_DEBUG, default=False, private=True) + _iast_propagation_debug = Env.var(bool, IAST.ENV_PROPAGATION_DEBUG, default=False, private=True) _appsec_standalone_enabled = Env.var(bool, APPSEC.STANDALONE_ENV, default=False) _use_metastruct_for_triggers = False @@ -175,6 +177,9 @@ class ASMConfig(Env): "_asm_obfuscation_parameter_value_regexp", "_appsec_standalone_enabled", "_iast_enabled", + "_iast_request_sampling", + "_iast_debug", + "_iast_propagation_debug", "_ep_enabled", "_use_metastruct_for_triggers", "_automatic_login_events_mode", diff --git a/tests/appsec/appsec/test_constants.py b/tests/appsec/appsec/test_constants.py index 121b29d6380..e9156c4b5e1 100644 --- a/tests/appsec/appsec/test_constants.py +++ b/tests/appsec/appsec/test_constants.py @@ -8,7 +8,6 @@ def test_not_deprecated(): with warnings.catch_warnings(record=True) as warns: warnings.simplefilter("always") - assert ddtrace.constants.IAST_ENV assert ddtrace.constants.APPSEC_ENV assert len(warns) == 0 diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index 2947c95d473..50d63f133f3 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -6,6 +6,7 @@ from requests.exceptions import ConnectionError +from ddtrace.appsec._constants import IAST from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.utils.retry import RetryError from ddtrace.vendor import psutil @@ -104,11 +105,12 @@ def appsec_application_server( # being equivalent to `appsec_enabled and apm_tracing_enabled` env["DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED"] = appsec_standalone_enabled if iast_enabled is not None and iast_enabled != "false": - env["DD_IAST_ENABLED"] = iast_enabled - env["DD_IAST_REQUEST_SAMPLING"] = "100" + env[IAST.ENV] = iast_enabled + env[IAST.ENV_REQUEST_SAMPLING] = "100" env["_DD_APPSEC_DEDUPLICATION_ENABLED"] = "false" if assert_debug: - env["_DD_IAST_DEBUG"] = iast_enabled + env["_" + IAST.ENV_DEBUG] = iast_enabled + env["_" + IAST.ENV_PROPAGATION_DEBUG] = iast_enabled env["DD_TRACE_DEBUG"] = iast_enabled if tracer_enabled is not None: env["DD_TRACE_ENABLED"] = tracer_enabled diff --git a/tests/appsec/iast/_ast/conftest.py b/tests/appsec/iast/_ast/conftest.py index e1791e58ef2..5942aa90e4b 100644 --- a/tests/appsec/iast/_ast/conftest.py +++ b/tests/appsec/iast/_ast/conftest.py @@ -2,14 +2,12 @@ from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce -from tests.utils import override_env from tests.utils import override_global_config @pytest.fixture(autouse=True) def iast_create_context(): - env = {"DD_IAST_REQUEST_SAMPLING": "100"} - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/_ast/test_ast_patching_type_hints.py b/tests/appsec/iast/_ast/test_ast_patching_type_hints.py index 3aec55011bf..19a1ddca73d 100644 --- a/tests/appsec/iast/_ast/test_ast_patching_type_hints.py +++ b/tests/appsec/iast/_ast/test_ast_patching_type_hints.py @@ -1,9 +1,35 @@ # -*- encoding: utf-8 -*- import sys +from typing import Text import pytest -from ddtrace.appsec._iast._utils import _get_patched_code + +def _get_patched_code(module_path: Text, module_name: Text) -> str: + """ + Print the patched code to stdout, for debugging purposes. + """ + import astunparse + + from ddtrace.appsec._iast._ast.ast_patching import get_encoding + from ddtrace.appsec._iast._ast.ast_patching import visit_ast + + with open(module_path, "r", encoding=get_encoding(module_path)) as source_file: + source_text = source_file.read() + + new_source = visit_ast( + source_text, + module_path, + module_name=module_name, + ) + + # If no modifications are done, + # visit_ast returns None + if not new_source: + return "" + + new_code = astunparse.unparse(new_source) + return new_code @pytest.mark.skipif(sys.version_info <= (3, 8, 0), reason="Sample code not compatible with Python 3.7") diff --git a/tests/appsec/iast/aspects/conftest.py b/tests/appsec/iast/aspects/conftest.py index 287f12d2067..51e9d7b2190 100644 --- a/tests/appsec/iast/aspects/conftest.py +++ b/tests/appsec/iast/aspects/conftest.py @@ -7,7 +7,6 @@ from ddtrace.appsec._iast._ast.ast_patching import astpatch_module from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce -from tests.utils import override_env from tests.utils import override_global_config @@ -34,8 +33,7 @@ def _iast_patched_module(module_name, new_module_object=False): @pytest.fixture(autouse=True) def iast_create_context(): - env = {"DD_IAST_REQUEST_SAMPLING": "100"} - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100)): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index eecf90d2520..8e75ed3d1b9 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -4,7 +4,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges @@ -16,7 +15,7 @@ from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce -from tests.utils import override_env +from tests.utils import override_global_config @pytest.mark.parametrize( @@ -262,7 +261,7 @@ def test_taint_object_error_with_no_context(log_level, iast_debug, caplog): assert len(ranges_result) == 1 _end_iast_context_and_oce() - with override_env({IAST.ENV_DEBUG: iast_debug}), caplog.at_level(log_level): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(log_level): result = taint_pyobject( pyobject=string_to_taint, source_name="test_add_aspect_tainting_left_hand", @@ -320,7 +319,7 @@ def test_propagate_ranges_with_no_context(caplog): ) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result_2 = add_aspect(result, "another_string") create_context() diff --git a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py index 168b50d3834..e746800cd3f 100644 --- a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py +++ b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py @@ -3,7 +3,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -12,7 +11,7 @@ from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") @@ -119,7 +118,7 @@ def test_propagate_ranges_with_no_context(caplog): pyobject=bytearray(b"456"), source_name="test", source_value="foo", source_origin=OriginType.PARAMETER ) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_bytearray_extend(ba1, ba2) assert result == bytearray(b"123456") log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py index 626d888d99d..a35a424d67b 100644 --- a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py @@ -7,7 +7,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence from ddtrace.appsec._iast._taint_tracking import create_context @@ -17,7 +16,7 @@ from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.aspect_utils import create_taint_range_with_format from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") @@ -246,7 +245,7 @@ def test_propagate_ranges_with_no_context(caplog): source_origin=OriginType.PARAMETER, ) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result_2 = mod.do_args_kwargs_4(string_input, 6, test_var=1) ranges_result = get_tainted_ranges(result_2) diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 71099db74c4..27282defb7b 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -3,7 +3,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges @@ -11,7 +10,7 @@ from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import flaky -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") @@ -115,7 +114,7 @@ def test_propagate_ranges_with_no_context(caplog): assert get_tainted_ranges(string_input) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_index(string_input, 3) assert result == "d" log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py index 8128b7c9b5c..8692485f295 100644 --- a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py @@ -4,14 +4,13 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") @@ -535,7 +534,7 @@ def test_propagate_ranges_with_no_context(caplog): ) it = ["a", "b", "c"] reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, it) assert result == "a-joiner-b-joiner-c" log_messages = [record.message for record in caplog.get_records("call")] @@ -554,7 +553,7 @@ def test_propagate_ranges_with_no_context_with_var(caplog): "c", ] reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, it) assert result == "a-joiner-b-joiner-c" log_messages = [record.message for record in caplog.get_records("call")] @@ -572,7 +571,7 @@ def test_propagate_ranges_with_no_context_with_equal_var(caplog): ) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, [a_tainted, a_tainted, a_tainted]) assert result == "abcdef-joiner-abcdef-joiner-abcdef" log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py index a1175f4ddeb..68ac2cba76e 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py @@ -5,7 +5,6 @@ import mock import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -14,7 +13,7 @@ from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.module_functions") @@ -162,7 +161,7 @@ def test_propagate_ranges_with_no_context(caplog): assert get_tainted_ranges(string_input) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_os_path_join(string_input, "bar") assert result == "abcde/bar" log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py index 4af9c2f779d..bd42b136e06 100644 --- a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py @@ -4,14 +4,13 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import override_env +from tests.utils import override_global_config mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") @@ -319,7 +318,7 @@ def test_propagate_ranges_with_no_context(caplog): ) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = mod.do_slice(tainted_input, 0, 3, None) assert result == "abc" log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/aspects/test_split_aspect.py b/tests/appsec/iast/aspects/test_split_aspect.py index 75229a09da7..30f4fe121ca 100644 --- a/tests/appsec/iast/aspects/test_split_aspect.py +++ b/tests/appsec/iast/aspects/test_split_aspect.py @@ -3,7 +3,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -16,7 +15,7 @@ from ddtrace.appsec._iast._taint_tracking import set_ranges from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.test_aspect_helpers import _build_sample_range -from tests.utils import override_env +from tests.utils import override_global_config def wrap_somesplit(func, *args, **kwargs): @@ -175,7 +174,7 @@ def test_propagate_ranges_with_no_context(caplog): assert get_ranges(string_input) reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): result = wrap_somesplit(_aspect_split, string_input, "|") assert result == ["abc", "def"] log_messages = [record.getMessage() for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 037754cb63a..793afc27c7c 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -31,7 +31,7 @@ def no_request_sampling(tracer): with override_env( { - "DD_IAST_REQUEST_SAMPLING": "100", + IAST.ENV_REQUEST_SAMPLING: "100", "DD_IAST_MAX_CONCURRENT_REQUEST": "100", } ): @@ -53,7 +53,7 @@ def _end_iast_context_and_oce(span=None): oce.release_request() -def iast_context(env, request_sampling="100", deduplication=False, asm_enabled=False): +def iast_context(env, request_sampling=100.0, deduplication=False, asm_enabled=False): try: from ddtrace.contrib.langchain.patch import patch as langchain_patch from ddtrace.contrib.langchain.patch import unpatch as langchain_unpatch @@ -76,10 +76,15 @@ def iast_context(env, request_sampling="100", deduplication=False, asm_enabled=F class MockSpan: _trace_id_64bits = 17577308072598193742 - env.update({"DD_IAST_REQUEST_SAMPLING": request_sampling, "_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) + env.update({"_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) VulnerabilityBase._reset_cache_for_testing() with override_global_config( - dict(_asm_enabled=asm_enabled, _iast_enabled=True, _deduplication_enabled=deduplication) + dict( + _asm_enabled=asm_enabled, + _iast_enabled=True, + _deduplication_enabled=deduplication, + _iast_request_sampling=request_sampling, + ) ), override_env(env): _start_iast_context_and_oce(MockSpan()) weak_hash_patch() @@ -132,7 +137,7 @@ def check_native_code_exception_in_each_python_aspect_test(request, caplog): if "skip_iast_check_logs" in request.keywords: yield else: - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): yield log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/taint_tracking/conftest.py b/tests/appsec/iast/taint_tracking/conftest.py index e1791e58ef2..b08bb398a27 100644 --- a/tests/appsec/iast/taint_tracking/conftest.py +++ b/tests/appsec/iast/taint_tracking/conftest.py @@ -2,14 +2,12 @@ from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce -from tests.utils import override_env from tests.utils import override_global_config @pytest.fixture(autouse=True) def iast_create_context(): - env = {"DD_IAST_REQUEST_SAMPLING": "100"} - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, request_sampling=100.0)): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range.py b/tests/appsec/iast/taint_tracking/test_native_taint_range.py index e676324d63d..d1683b5ffb4 100644 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range.py +++ b/tests/appsec/iast/taint_tracking/test_native_taint_range.py @@ -10,7 +10,6 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange @@ -33,7 +32,7 @@ from ddtrace.appsec._iast._taint_tracking.aspects import format_aspect from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect from tests.appsec.iast.conftest import IAST_VALID_LOG -from tests.utils import override_env +from tests.utils import override_global_config def test_source_origin_refcount(): @@ -500,7 +499,7 @@ def test_race_conditions_reset_contexts_threads(caplog, telemetry_writer): """we want to validate context is working correctly among multiple request and no race condition creating and destroying contexts """ - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): pool = ThreadPool(processes=3) results_async = [pool.apply_async(reset_contexts_loop) for _ in range(70)] _ = [res.get() for res in results_async] diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index 7878bf3045e..90d9b0c064a 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -3,10 +3,10 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Source from tests.utils import override_env +from tests.utils import override_global_config with override_env({"DD_IAST_ENABLED": "True"}): @@ -47,7 +47,7 @@ def test_taint_object_with_no_context_should_be_noop(): @pytest.mark.skip_iast_check_logs def test_propagate_ranges_with_no_context(caplog): reset_context() - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): string_input = taint_pyobject( pyobject="abcde", source_name="abcde", source_value="abcde", source_origin=OriginType.PARAMETER ) diff --git a/tests/appsec/iast/test_overhead_control_engine.py b/tests/appsec/iast/test_overhead_control_engine.py index c0ed40bb041..318f1a2104f 100644 --- a/tests/appsec/iast/test_overhead_control_engine.py +++ b/tests/appsec/iast/test_overhead_control_engine.py @@ -3,12 +3,11 @@ import pytest -from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._iast_request_context import get_iast_reporter from ddtrace.appsec._iast._overhead_control_engine import MAX_REQUESTS from ddtrace.appsec._iast._overhead_control_engine import MAX_VULNERABILITIES_PER_REQUEST -from tests.utils import override_env +from tests.utils import override_global_config def function_with_vulnerabilities_3(tracer): @@ -177,9 +176,9 @@ def test_oce_concurrent_requests_futures_in_spans(tracer, iast_span_defaults, ca results = [] num_requests = 5 - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG), concurrent.futures.ThreadPoolExecutor( - max_workers=5 - ) as executor: + with override_global_config(dict(_iast_debug=True)), caplog.at_level( + logging.DEBUG + ), concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: futures = [] for _ in range(0, num_requests): futures.append(executor.submit(function_with_vulnerabilities_1, tracer)) diff --git a/tests/appsec/iast/test_processor.py b/tests/appsec/iast/test_processor.py index 877da0f7830..3deaa60a530 100644 --- a/tests/appsec/iast/test_processor.py +++ b/tests/appsec/iast/test_processor.py @@ -11,6 +11,7 @@ from ddtrace.ext import SpanTypes from tests.utils import DummyTracer from tests.utils import override_env +from tests.utils import override_global_config def traced_function(tracer): @@ -64,13 +65,19 @@ def test_appsec_iast_processor_ensure_span_is_manual_keep(iast_context_defaults, @pytest.mark.skip_iast_check_logs -@pytest.mark.parametrize("sampling_rate", ["0.0", "100"]) +@pytest.mark.parametrize("sampling_rate", [0.0, "100"]) def test_appsec_iast_processor_ensure_span_is_sampled(iast_context_defaults, sampling_rate): """ test_appsec_iast_processor_ensure_span_is_manual_keep. This test throws 'finished span not connected to a trace' log error """ - with override_env(dict(DD_IAST_REQUEST_SAMPLING=sampling_rate)): + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + _iast_request_sampling=sampling_rate, + ) + ): oce.reconfigure() tracer = DummyTracer(iast_enabled=True) @@ -78,7 +85,7 @@ def test_appsec_iast_processor_ensure_span_is_sampled(iast_context_defaults, sam tracer._on_span_finish(span) result = span.get_tag(IAST.JSON) - if sampling_rate == "0.0": + if sampling_rate == 0.0: assert result is None assert span.get_metric(SAMPLING_PRIORITY_KEY) is AUTO_KEEP assert span.get_metric(IAST.ENABLED) == 0.0 diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index a848e33cd4c..ee4b7082526 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -2,8 +2,8 @@ from ddtrace.appsec._common_module_patches import patch_common_modules from ddtrace.appsec._common_module_patches import unpatch_common_modules -from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._handlers import _on_django_patch from ddtrace.appsec._iast._metrics import TELEMETRY_DEBUG_VERBOSITY from ddtrace.appsec._iast._metrics import TELEMETRY_INFORMATION_VERBOSITY @@ -163,8 +163,9 @@ def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): def test_metric_request_tainted(no_request_sampling, telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) + dict(_iast_enabled=True, _iast_request_sampling=100.0) ): + oce.reconfigure() tracer = DummyTracer(iast_enabled=True) with tracer.trace("test", span_type=SpanTypes.WEB) as span: @@ -188,7 +189,7 @@ def test_metric_request_tainted(no_request_sampling, telemetry_writer): @pytest.mark.skip_iast_check_logs def test_log_metric(telemetry_writer): - with override_env({IAST.ENV_DEBUG: "true"}): + with override_global_config(dict(_iast_debug=True)): _set_iast_error_metric("test_format_key_error_and_no_log_metric raises") list_metrics_logs = list(telemetry_writer._logs) @@ -199,7 +200,7 @@ def test_log_metric(telemetry_writer): @pytest.mark.skip_iast_check_logs def test_log_metric_debug_disabled(telemetry_writer): - with override_env({IAST.ENV_DEBUG: "false"}): + with override_global_config(dict(_iast_debug=False)): _set_iast_error_metric("test_log_metric_debug_disabled raises") list_metrics_logs = list(telemetry_writer._logs) @@ -210,7 +211,7 @@ def test_log_metric_debug_disabled(telemetry_writer): @pytest.mark.skip_iast_check_logs def test_log_metric_debug_disabled_deduplication(telemetry_writer): - with override_env({IAST.ENV_DEBUG: "false"}): + with override_global_config(dict(_iast_debug=False)): for i in range(10): _set_iast_error_metric("test_log_metric_debug_disabled_deduplication raises") @@ -222,7 +223,7 @@ def test_log_metric_debug_disabled_deduplication(telemetry_writer): @pytest.mark.skip_iast_check_logs def test_log_metric_debug_disabled_deduplication_different_messages(telemetry_writer): - with override_env({IAST.ENV_DEBUG: "false"}): + with override_global_config(dict(_iast_debug=False)): for i in range(10): _set_iast_error_metric(f"test_format_key_error_and_no_log_metric raises {i}") @@ -233,7 +234,7 @@ def test_log_metric_debug_disabled_deduplication_different_messages(telemetry_wr def test_django_instrumented_metrics(telemetry_writer): - with override_global_config(dict(_iast_enabled=True)): + with override_global_config(dict(_iast_enabled=True, _iast_debug=True)): _on_django_patch() metrics_result = telemetry_writer._namespace._metrics_data diff --git a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py index e919c9704d7..5749c7e7f64 100644 --- a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py +++ b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py @@ -1,13 +1,11 @@ import pytest -from tests.utils import override_env from tests.utils import override_global_config @pytest.mark.asyncio async def test_aggregated_leaks(): - env = {"DD_IAST_ENABLED": "true", "DD_IAST_REQUEST_SAMPLING": "100"} - with override_env(env), override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): from scripts.iast.leak_functions import iast_leaks result = await iast_leaks(75000, 1.0, 100) == 0 diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 91aa4e7efde..aa9507a4241 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -9,7 +9,6 @@ import pytest from ddtrace.appsec._constants import IAST -from ddtrace.constants import IAST_ENV from tests.appsec.appsec_utils import flask_server from tests.utils import override_env @@ -1031,7 +1030,7 @@ def test_packages_patched_import(package, venv): "True" if package.expect_no_change else "False", ] - with override_env({IAST_ENV: "true"}): + with override_env({IAST.ENV: "true"}): # 1. Try with the specified version package.install(venv) result = subprocess.run( diff --git a/tests/appsec/integrations/test_psycopg2.py b/tests/appsec/integrations/test_psycopg2.py index d1998eff6ca..3e08670f2d1 100644 --- a/tests/appsec/integrations/test_psycopg2.py +++ b/tests/appsec/integrations/test_psycopg2.py @@ -6,14 +6,12 @@ from ddtrace.appsec._iast._taint_utils import LazyTaintList from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce -from tests.utils import override_env from tests.utils import override_global_config @pytest.fixture(autouse=True) def iast_create_context(): - env = {"DD_IAST_REQUEST_SAMPLING": "100"} - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(env): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/contrib/dbapi/test_dbapi_appsec.py b/tests/contrib/dbapi/test_dbapi_appsec.py index 81e8971f271..0ee86f99685 100644 --- a/tests/contrib/dbapi/test_dbapi_appsec.py +++ b/tests/contrib/dbapi/test_dbapi_appsec.py @@ -10,13 +10,9 @@ from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.utils import TracerTestCase -from tests.utils import override_env from tests.utils import override_global_config -IAST_ENV = {"DD_IAST_ENABLED": "True", "DD_IAST_REQUEST_SAMPLING": "100"} - - class TestTracedCursor(TracerTestCase): def setUp(self): super(TestTracedCursor, self).setUp() @@ -24,19 +20,17 @@ def setUp(self): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) - ), override_env(IAST_ENV): + ): _start_iast_context_and_oce() self.cursor = mock.Mock() self.cursor.execute.__name__ = "execute" def tearDown(self): with override_global_config( - dict( - _iast_enabled=True, - _deduplication_enabled=False, - ) - ), override_env(IAST_ENV): + dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0) + ): _end_iast_context_and_oce() @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index 31c69c3fc11..89495dcac80 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -26,7 +26,7 @@ @pytest.fixture(autouse=True) def iast_context(): with override_env( - {IAST.ENV: "True", "DD_IAST_REQUEST_SAMPLING": "100", "_DD_APPSEC_DEDUPLICATION_ENABLED": "false"} + {IAST.ENV: "True", IAST.ENV_REQUEST_SAMPLING: "100", "_DD_APPSEC_DEDUPLICATION_ENABLED": "false"} ): yield @@ -41,7 +41,7 @@ def check_native_code_exception_in_each_django_test(request, caplog, telemetry_w yield else: caplog.set_level(logging.DEBUG) - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): yield log_messages = [record.message for record in caplog.get_records("call")] @@ -110,7 +110,7 @@ def _aux_appsec_get_root_span_with_exception( @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_weak_hash(client, test_spans, tracer): - with override_global_config(dict(_asm_enabled=True, _iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): oce.reconfigure() patch_iast({"weak_hash": True}) root_span, _ = _aux_appsec_get_root_span(client, test_spans, tracer, url="/appsec/weak-hash/") @@ -157,15 +157,15 @@ def test_django_tainted_user_agent_iast_enabled(client, test_spans, tracer): @pytest.mark.parametrize( "sampling", [ - "0", - "100", - "50", + 0.0, + 100.0, + 50.0, ], ) @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_view_with_exception(client, test_spans, tracer, payload, content_type, deduplication, sampling): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=deduplication)), override_env( - {"DD_IAST_REQUEST_SAMPLING": sampling} + with override_global_config( + dict(_iast_enabled=True, _deduplication_enabled=deduplication, _iast_request_sampling=sampling) ): response = _aux_appsec_get_root_span_with_exception( client, @@ -591,16 +591,16 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_body(client, test_span @pytest.mark.parametrize( "sampling", [ - "0", - "100", - "50", + 0.0, + 100.0, + 50.0, ], ) @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_http_body_empty(client, test_spans, tracer, payload, content_type, deduplication, sampling): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=deduplication)), override_env( - {"DD_IAST_REQUEST_SAMPLING": sampling} + with override_global_config( + dict(_iast_enabled=True, _deduplication_enabled=deduplication, _iast_request_sampling=sampling) ): root_span, response = _aux_appsec_get_root_span( client, diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 0c8f538c743..39b1b9c20b1 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -24,12 +24,9 @@ from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash -from tests.utils import override_env from tests.utils import override_global_config -IAST_ENV = {"DD_IAST_REQUEST_SAMPLING": "100"} - TEST_FILE_PATH = "tests/contrib/fastapi/test_fastapi_appsec_iast.py" fastapi_version = tuple([int(v) for v in _fastapi_version.split(".")]) @@ -60,7 +57,7 @@ def check_native_code_exception_in_each_fastapi_test(request, caplog, telemetry_ yield else: caplog.set_level(logging.DEBUG) - with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): yield log_messages = [record.msg for record in caplog.get_records("call")] @@ -90,7 +87,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -125,7 +122,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -161,7 +158,7 @@ async def test_route(iast_header: typing.Annotated[str, Header()] = None): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -195,7 +192,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -231,7 +228,7 @@ async def test_route(iast_cookie: typing.Annotated[str, Cookie()] = "ddd"): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -265,7 +262,7 @@ async def test_route(item_id): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -299,7 +296,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -334,7 +331,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.post( @@ -370,7 +367,7 @@ async def test_route(request: Request): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.post( @@ -407,7 +404,7 @@ async def test_route(path: typing.Annotated[str, Form()]): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.post("/index.html", data={"path": "/var/log"}) @@ -438,7 +435,7 @@ async def test_route(path: str = Form(...)): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.post("/index.html", data={"path": "/var/log"}) @@ -478,7 +475,7 @@ async def test_route(item: Item): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.post( @@ -507,7 +504,7 @@ async def create_upload_file(files: typing.List[UploadFile]): } ) - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) tmp = io.BytesIO(b"upload this") @@ -539,7 +536,7 @@ async def test_route(param_str): # label test_fastapi_sqli_path_parameter cur.execute(add_aspect("SELECT 1 FROM ", param_str)) - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index d9c097d9ac8..f5b8774a140 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -17,17 +17,12 @@ from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks.header_injection import patch as patch_header_injection from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli -from tests.appsec.iast.conftest import _end_iast_context_and_oce -from tests.appsec.iast.conftest import _start_iast_context_and_oce from tests.appsec.iast.iast_utils import get_line_and_hash from tests.contrib.flask import BaseFlaskTestCase -from tests.utils import override_env from tests.utils import override_global_config TEST_FILE_PATH = "tests/contrib/flask/test_flask_appsec_iast.py" -IAST_ENV = {"DD_IAST_REQUEST_SAMPLING": "100"} -IAST_ENV_SAMPLING_0 = {"DD_IAST_REQUEST_SAMPLING": "0"} werkzeug_version = version("werkzeug") flask_version = tuple([int(v) for v in version("flask").split(".")]) @@ -43,28 +38,18 @@ def setUp(self): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) - ), override_env(IAST_ENV): + ): super(FlaskAppSecIASTEnabledTestCase, self).setUp() patch_sqlite_sqli() patch_header_injection() patch_json() - oce.reconfigure() - _start_iast_context_and_oce() self.tracer._iast_enabled = True self.tracer._asm_enabled = True self.tracer.configure(api_version="v0.4") - - def tearDown(self): - with override_global_config( - dict( - _iast_enabled=True, - _deduplication_enabled=False, - ) - ), override_env(IAST_ENV): - _end_iast_context_and_oce() - super(FlaskAppSecIASTEnabledTestCase, self).tearDown() + oce.reconfigure() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_flask_full_sqli_iast_http_request_path_parameter(self): @@ -318,6 +303,8 @@ def sqli_5(param_str, param_int): with override_global_config( dict( _iast_enabled=True, + _deduplication_enabled=False, + _iast_request_sampling=100.0, ) ): resp = self.client.post("/sqli/hello/1000/?select%20from%20table", data={"name": "test"}) @@ -349,13 +336,7 @@ def sqli_6(param_str): class MockSpan: _trace_id_64bits = 17577308072598193742 - _end_iast_context_and_oce() - with override_global_config( - dict( - _iast_enabled=True, - _deduplication_enabled=False, - ) - ), override_env(IAST_ENV_SAMPLING_0): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=0.0)): oce.reconfigure() _iast_start_request(MockSpan()) resp = self.client.post("/sqli/hello/?select%20from%20table", data={"name": "test"}) @@ -386,8 +367,9 @@ def sqli_7(): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) - ), override_env(IAST_ENV): + ): oce.reconfigure() if tuple(map(int, werkzeug_version.split("."))) >= (2, 3): @@ -563,6 +545,7 @@ def sqli_10(): json_data = json.loads(request.data) value = json_data.get("json_body") assert value == "master" + assert is_pyobject_tainted(value) query = add_aspect(add_aspect("SELECT tbl_name FROM sqlite_", value), " WHERE tbl_name LIKE 'password'") # label test_flask_request_body @@ -574,6 +557,7 @@ def sqli_10(): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) ): resp = self.client.post( @@ -1169,6 +1153,7 @@ def no_http_only_cookie_empty(): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) ): resp = self.client.post("/no_http_only_cookie_empty/", data={"name": "test"}) @@ -1265,6 +1250,7 @@ def cookie_secure(): dict( _iast_enabled=True, _deduplication_enabled=False, + _iast_request_sampling=100.0, ) ): resp = self.client.post("/cookie_secure/", data={"name": "test"}) @@ -1286,8 +1272,9 @@ def setUp(self): with override_global_config( dict( _iast_enabled=False, + _iast_request_sampling=100.0, ) - ), override_env({"DD_IAST_REQUEST_SAMPLING": "100"}): + ): super(FlaskAppSecIASTDisabledTestCase, self).setUp() self.tracer._iast_enabled = False self.tracer._asm_enabled = False diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 92385ceb142..d90a7351eba 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -369,6 +369,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python "[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(\\.[\\w.+\\/=-]+)?|[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}" "[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}", }, + {"name": "DD_IAST_REQUEST_SAMPLING", "origin": "default", "value": 30.0}, {"name": "DD_INJECT_FORCE", "origin": "env_var", "value": True}, {"name": "DD_INSTRUMENTATION_INSTALL_ID", "origin": "default", "value": None}, {"name": "DD_INSTRUMENTATION_INSTALL_TYPE", "origin": "default", "value": None}, From 6d8ee31ead0d76adc2d1516f0f2990c6dd71cb26 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 21 Oct 2024 11:42:43 +0200 Subject: [PATCH 027/372] test(ci): iast prechecks (#11097) fix CI state ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/fastapi/test_fastapi_appsec_iast.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 39b1b9c20b1..5c95e72bdb4 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -592,7 +592,7 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -635,7 +635,7 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -670,7 +670,7 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -713,7 +713,7 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -748,7 +748,7 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env(IAST_ENV): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", From cf2b638bbbcea230be7845aaeeeb680864d79c94 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:59:24 +0200 Subject: [PATCH 028/372] chore(asm): add metadata to rc data pass to the WAF (#11077) Ensure the right rule_version is sent by span tags and telemetry: - read the rule version from rc payload metadata - add a cache to keep the last rule version information in the waf - add metadata regression test for rc This improvement will be tested E2E in system tests (https://github.com/DataDog/system-tests/pull/3256) APPSEC-55311 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_ddwaf/__init__.py | 4 +- ddtrace/appsec/_remoteconfiguration.py | 20 +++--- .../appsec/appsec/test_remoteconfiguration.py | 72 ++++++++++++------- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/ddtrace/appsec/_ddwaf/__init__.py b/ddtrace/appsec/_ddwaf/__init__.py index 153f13d1849..8b1f487e6a4 100644 --- a/ddtrace/appsec/_ddwaf/__init__.py +++ b/ddtrace/appsec/_ddwaf/__init__.py @@ -98,6 +98,7 @@ def __init__( diagnostics = ddwaf_object() ruleset_map_object = ddwaf_object.create_without_limits(ruleset_map) self._handle = py_ddwaf_init(ruleset_map_object, ctypes.byref(config), ctypes.byref(diagnostics)) + self._cached_version = "" self._set_info(diagnostics) info = self.info if not self._handle or info.failed: @@ -119,7 +120,8 @@ def _set_info(self, diagnostics: ddwaf_object) -> None: info_struct = diagnostics.struct rules = info_struct.get("rules", {}) if info_struct else {} # type: ignore errors_result = rules.get("errors", {}) - version = info_struct.get("ruleset_version", "") if info_struct else "" # type: ignore + version = info_struct.get("ruleset_version", self._cached_version) if info_struct else self._cached_version # type: ignore + self._cached_version = version self._info = DDWaf_info(len(rules.get("loaded", [])), len(rules.get("failed", [])), errors_result, version) ddwaf_object_free(diagnostics) diff --git a/ddtrace/appsec/_remoteconfiguration.py b/ddtrace/appsec/_remoteconfiguration.py index 87eadbb7d26..4aa7910773c 100644 --- a/ddtrace/appsec/_remoteconfiguration.py +++ b/ddtrace/appsec/_remoteconfiguration.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import json import os from typing import Any from typing import Dict @@ -89,14 +88,17 @@ def disable_appsec_rc(): def _add_rules_to_list(features: Mapping[str, Any], feature: str, message: str, ruleset: Dict[str, Any]) -> None: rules = features.get(feature, None) if rules is not None: - try: - if ruleset.get(feature) is None: - ruleset[feature] = rules + if ruleset.get(feature) is None: + ruleset[feature] = rules + else: + current_rules = ruleset[feature] + if isinstance(rules, list) and isinstance(current_rules, list): + ruleset[feature] = current_rules + rules + elif isinstance(rules, dict) and isinstance(current_rules, dict): + ruleset[feature] = {**current_rules, **rules} else: - ruleset[feature] = ruleset[feature] + rules - log.debug("Reloading Appsec %s: %s", message, str(rules)[:20]) - except json.JSONDecodeError: - log.error("ERROR Appsec %s: invalid JSON content from remote configuration", message) + log.debug("Invalid type for %s: %s with %s", message, str(type(current_rules)), str(type(rules))) + log.debug("Reloading Appsec %s: %s", message, str(rules)[:20]) def _appsec_callback(features: Mapping[str, Any], test_tracer: Optional[Tracer] = None) -> None: @@ -128,6 +130,8 @@ def _appsec_rules_data(features: Mapping[str, Any], test_tracer: Optional[Tracer _add_rules_to_list(features, "rules_data", "rules data", ruleset) _add_rules_to_list(features, "rules_override", "rules override", ruleset) _add_rules_to_list(features, "scanners", "scanners", ruleset) + _add_rules_to_list(features, "metadata", "metadata", ruleset) + if ruleset: return tracer._appsec_processor._update_rules({k: v for k, v in ruleset.items() if v is not None}) diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index 82fe04f61e0..ade6543f8c9 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -991,32 +991,52 @@ def test_rc_activation_ip_blocking_data_not_expired(tracer, remote_config_worker def test_rc_rules_data(tracer): - RULES_PATH = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(rules.ROOT_DIR))), "ddtrace/appsec/rules.json" - ) - with override_env({APPSEC.ENV: "true"}), override_global_config(dict(_asm_enabled=True)), open( - RULES_PATH, "r" - ) as dd_rules: - tracer.configure(appsec_enabled=True, api_version="v0.4") - config = { - "rules_data": [], - "custom_rules": [], - "actions": [], - "rules": json.load(dd_rules)["rules"], - "rules_override": [], - "scanners": [], - "processors": [], - "ignore": [], - } - with mock.patch("ddtrace.appsec._processor.AppSecSpanProcessor._update_rules", autospec=True) as mock_update: - mock_update.reset_mock() - _appsec_rules_data(config, tracer) - calls = mock_update.mock_calls - for v in config: - if v == "ignore": - assert v not in calls[-1][1][1] - else: - assert v in calls[-1][1][1] + import tempfile + + STATIC_RULE_FILE = { + "custom_rules": [], + "actions": ["action1"], + "metadata": {"version": "0.4", "tmp": "test"}, + "scanners": {"test": "test"}, + "rules": [], + } + + with tempfile.NamedTemporaryFile() as f: + f.write(json.dumps(STATIC_RULE_FILE).encode()) + f.flush() + with override_env({APPSEC.ENV: "true"}), override_global_config( + dict(_asm_enabled=True, _asm_static_rule_file=f.name) + ): + tracer.configure(appsec_enabled=True, api_version="v0.4") + config = { + "rules_data": [], + "custom_rules": [], + "actions": ["action2"], + "rules_override": [], + "scanners": ["test"], + "processors": [], + "metadata": {"id": "0.3", "tmp": "new"}, + "ignore": [], + "rules": [], # rules are empty, so we should merge the static rules + } + with mock.patch( + "ddtrace.appsec._processor.AppSecSpanProcessor._update_rules", autospec=True + ) as mock_update: + mock_update.reset_mock() + _appsec_rules_data(config, tracer) + calls = mock_update.mock_calls + struct_sent = calls[-1][1][1] + for v in config: + if v == "ignore": + assert v not in struct_sent + else: + assert v in struct_sent + # test if merged dict + assert struct_sent["metadata"] == {"version": "0.4", "id": "0.3", "tmp": "new"} + # test if merged list + assert struct_sent["actions"] == ["action1", "action2"] + # test if ignoring list after dict + assert struct_sent["scanners"] == {"test": "test"} def test_rc_rules_data_error_empty(tracer): From b6c85b1c5ce2cb516abba119dbe611cd13833c3a Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 21 Oct 2024 12:54:10 +0100 Subject: [PATCH 029/372] chore(di): clean up run module logic (#11082) We clean up the run module logic and improve the detection of the `__main__` module. We also fix some typing. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 2 +- ddtrace/internal/module.py | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index abc5cf8796b..74e263fc59f 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -281,7 +281,7 @@ class Debugger(Service): __logger__ = ProbeStatusLogger @classmethod - def enable(cls, run_module: bool = False) -> None: + def enable(cls) -> None: """Enable dynamic instrumentation This class method is idempotent. Dynamic instrumentation will be diff --git a/ddtrace/internal/module.py b/ddtrace/internal/module.py index 6b5b8192773..c73b330804d 100644 --- a/ddtrace/internal/module.py +++ b/ddtrace/internal/module.py @@ -453,16 +453,19 @@ def __init__(self) -> None: super().__init__() self._hook_map: t.DefaultDict[str, t.List[ModuleHookType]] = defaultdict(list) - self._om: t.Optional[t.Dict[str, ModuleType]] = None + # DEV: It would make more sense to make this a mapping of Path to ModuleType + # but the WeakValueDictionary causes an ignored exception on shutdown + # because the pathlib module is being garbage collected. + self._om: t.Optional[t.MutableMapping[str, ModuleType]] = None # _pre_exec_module_hooks is a set of tuples (condition, hook) instead # of a list to ensure that no hook is duplicated self._pre_exec_module_hooks: t.Set[t.Tuple[PreExecHookCond, PreExecHookType]] = set() self._import_exception_hooks: t.Set[t.Tuple[ImportExceptionHookCond, ImportExceptionHookType]] = set() @property - def _origin_map(self) -> t.Dict[str, ModuleType]: - def modules_with_origin(modules: t.Iterable[ModuleType]) -> t.Dict[str, t.Any]: - result: wvdict = wvdict() + def _origin_map(self) -> t.MutableMapping[str, ModuleType]: + def modules_with_origin(modules: t.Iterable[ModuleType]) -> t.MutableMapping[str, ModuleType]: + result: t.MutableMapping[str, ModuleType] = wvdict() for m in modules: module_origin = origin(m) @@ -479,7 +482,7 @@ def modules_with_origin(modules: t.Iterable[ModuleType]) -> t.Dict[str, t.Any]: # information that can be used at the Python runtime level. pass - return t.cast(t.Dict[str, t.Any], result) + return result if self._om is None: try: @@ -526,7 +529,7 @@ def get_by_origin(cls, _origin: Path) -> t.Optional[ModuleType]: # Check if this is the __main__ module main_module = sys.modules.get("__main__") - if main_module is not None and origin(main_module) == path: + if main_module is not None and origin(main_module) == resolved_path: # Register for future lookups instance._origin_map[path] = main_module @@ -557,7 +560,11 @@ def register_origin_hook(cls, origin: Path, hook: ModuleHookType) -> None: instance = t.cast(ModuleWatchdog, cls._instance) instance._hook_map[path].append(hook) try: - module = instance._origin_map[path] + module = instance.get_by_origin(resolved_path) + if module is None: + # The path was resolved but we still haven't seen any module + # that has it as origin. Nothing more we can do for now. + return # Sanity check: the module might have been removed from sys.modules # but not yet garbage collected. try: From 2cd75fccc4ff61a9b90052ea39c86ec89645e0ac Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 21 Oct 2024 14:53:51 +0200 Subject: [PATCH 030/372] chore(iast): restore Match (#11099) IAST context refactor found a problem with tainted re.Match objects https://github.com/DataDog/dd-trace-py/pull/10988/files#diff-85e91d30afa95f5cf701f8187200d0bdcec50d55999ef03708d49fa5a5ccb1d6R127 It looks this PR fixed the problem https://github.com/DataDog/dd-trace-py/pull/11042 So, this PR enables again Match ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_constants.py | 4 ++-- ddtrace/appsec/_iast/_taint_tracking/__init__.py | 8 ++++---- tests/contrib/fastapi/test_fastapi_appsec_iast.py | 6 ------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index f7f9f3b12fc..4cf839d990b 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -1,4 +1,5 @@ import os +from re import Match import sys from _io import BytesIO @@ -125,8 +126,7 @@ class IAST(metaclass=Constant_Class): DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES" SEP_MODULES: Literal[","] = "," TEXT_TYPES = (str, bytes, bytearray) - # TODO(avara1986): `Match` contains errors. APPSEC-55239 - TAINTEABLE_TYPES = (str, bytes, bytearray, BytesIO, StringIO) + TAINTEABLE_TYPES = (str, bytes, bytearray, Match, BytesIO, StringIO) class IAST_SPAN_TAGS(metaclass=Constant_Class): diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 323d227c0ec..041159410f1 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -132,7 +132,7 @@ def iast_taint_log_error(msg): def is_pyobject_tainted(pyobject: Any) -> bool: if not is_iast_request_enabled(): return False - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return False try: @@ -146,7 +146,7 @@ def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, sou if not is_iast_request_enabled(): return pyobject - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return pyobject # We need this validation in different contition if pyobject is not a text type and creates a side-effect such as # __len__ magic method call. @@ -190,7 +190,7 @@ def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_or def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: if not is_iast_request_enabled(): return False - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return False try: set_ranges(pyobject, ranges) @@ -203,7 +203,7 @@ def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: def get_tainted_ranges(pyobject: Any) -> Tuple: if not is_iast_request_enabled(): return tuple() - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return tuple() try: return get_ranges(pyobject) diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 5c95e72bdb4..9688c7d06b7 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -603,11 +603,9 @@ def insecure_cookie(request: Request): assert span.get_metric(IAST.ENABLED) == 1.0 loaded = json.loads(span.get_tag(IAST.JSON)) - assert loaded["sources"] == [] assert len(loaded["vulnerabilities"]) == 1 vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_INSECURE_COOKIE - assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} assert "path" not in vulnerability["location"].keys() assert "line" not in vulnerability["location"].keys() assert vulnerability["location"]["spanId"] @@ -681,11 +679,9 @@ def insecure_cookie(request: Request): assert span.get_metric(IAST.ENABLED) == 1.0 loaded = json.loads(span.get_tag(IAST.JSON)) - assert loaded["sources"] == [] assert len(loaded["vulnerabilities"]) == 1 vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_NO_HTTPONLY_COOKIE - assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} assert "path" not in vulnerability["location"].keys() assert "line" not in vulnerability["location"].keys() assert vulnerability["location"]["spanId"] @@ -759,11 +755,9 @@ def insecure_cookie(request: Request): assert span.get_metric(IAST.ENABLED) == 1.0 loaded = json.loads(span.get_tag(IAST.JSON)) - assert loaded["sources"] == [] assert len(loaded["vulnerabilities"]) == 1 vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_NO_SAMESITE_COOKIE - assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} assert "path" not in vulnerability["location"].keys() assert "line" not in vulnerability["location"].keys() assert vulnerability["location"]["spanId"] From 57a31f6e4481b11ad426c88ceb7b2f5b7f20a461 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:53:32 +0200 Subject: [PATCH 031/372] chore(asm): add small fix for span in IASTEnvironment (#11103) Fix a recent mistake in a merge. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_iast_request_context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index eab39e0449d..becc2bcd49a 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -43,8 +43,7 @@ class IASTEnvironment: """ def __init__(self, span: Optional[Span] = None): - if span is None: - self.span: Span = core.get_item(core.get_item("call_key")) + self.span = span or core.get_span() self.request_enabled: bool = False self.iast_reporter: Optional[IastSpanReporter] = None From 486d0f837c98b533f387865f40a8e86cb0d9e3b5 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 21 Oct 2024 17:10:33 +0100 Subject: [PATCH 032/372] docs: pin envier dependency (#11104) We fix the dependency on envier for the doc environment to avoid breaking when a new version is released. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- hatch.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatch.toml b/hatch.toml index f3cf8a21b9b..8eef89a6a4e 100644 --- a/hatch.toml +++ b/hatch.toml @@ -83,7 +83,7 @@ dependencies = [ # copied from library dependencies "protobuf>=3", "typing_extensions", "xmltodict>=0.12", - "envier", + "envier==0.5.2", "opentelemetry-api>=1", "opentracing>=2.0.0", "bytecode", From 5279d1cacc8069118161c0a18c3ed760f25dd87f Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:13:22 +0200 Subject: [PATCH 033/372] chore(asm): update libddwaf to 1.20.1 (#11102) Update libddwaf from 1.20.0 to [1.20.1](https://github.com/DataDog/libddwaf/releases/tag/1.20.1) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- setup.py | 2 +- ....test_processor.test_appsec_body_no_collection_snapshot.json | 2 +- ...st_processor.test_appsec_cookies_no_collection_snapshot.json | 2 +- ...ec.appsec.test_processor.test_appsec_span_tags_snapshot.json | 2 +- ...st_processor.test_appsec_span_tags_snapshot_with_errors.json | 2 +- ...django.test_django_appsec_snapshots.test_appsec_enabled.json | 2 +- ...test_django_appsec_snapshots.test_appsec_enabled_attack.json | 2 +- ..._django_appsec_snapshots.test_request_ipblock_match_403.json | 2 +- ...go_appsec_snapshots.test_request_ipblock_match_403_json.json | 2 +- ...jango_appsec_snapshots.test_request_ipblock_nomatch_200.json | 2 +- ...st_flask_ipblock_match_403[flask_appsec_good_rules_env].json | 2 +- ...lask_ipblock_match_403[flask_appsec_good_rules_env]_220.json | 2 +- ...ask_ipblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...ipblock_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- ..._flask_processexec_osspawn[flask_appsec_good_rules_env].json | 2 +- ...sk_processexec_osspawn[flask_appsec_good_rules_env]_220.json | 2 +- ...flask_processexec_ossystem[flask_appsec_good_rules_env].json | 2 +- ...k_processexec_ossystem[flask_appsec_good_rules_env]_220.json | 2 +- ...bprocesscommunicatenoshell[flask_appsec_good_rules_env].json | 2 +- ...cesscommunicatenoshell[flask_appsec_good_rules_env]_220.json | 2 +- ...subprocesscommunicateshell[flask_appsec_good_rules_env].json | 2 +- ...rocesscommunicateshell[flask_appsec_good_rules_env]_220.json | 2 +- ...k_userblock_match_200_json[flask_appsec_good_rules_env].json | 2 +- ...erblock_match_200_json[flask_appsec_good_rules_env]_220.json | 2 +- ...k_userblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...erblock_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/setup.py b/setup.py index 93bb92a2fec..727de14e037 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ CURRENT_OS = platform.system() -LIBDDWAF_VERSION = "1.20.0" +LIBDDWAF_VERSION = "1.20.1" RUST_MINIMUM_VERSION = "1.71" # Safe guess: 1.71 is about a year old as of 2024-07-03 diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index 0a5e60b142e..b4e68b2b540 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.1", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index 01b3d9dba7b..2aa9610cccf 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.1", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index d5617a25b03..0bbc13be00e 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.1", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index c0c50427115..10e49c59b12 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index 6dbd270be1e..0d8f4ab4d8d 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "1.13.1", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index 7319b69a505..99e43862127 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.1", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json index 7ca8a094d04..91b403fd793 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":10192376353237234254}]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json index 9977bb5e614..80badf55332 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":865087550764298227}]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json index 4c10e172edd..bc4695d1779 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json index 1c9d9519784..28cc19763ff 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json index 860e0e22e25..cb9c1ce72de 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json index be96b5d1cee..7dbeab81e7a 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json index a28a21723ad..eb86dfdf683 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json index ecfe59bfbdb..94d43de577b 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json index bf35e291232..481dab6544d 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json index 6358b28d0df..f0d79286696 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json index 66813ae4ed8..3ea6ae304d8 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json index 8bf38e667e9..5cc68d6a6bb 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json index a542f1bf510..d37bbd839d7 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json index f83e8fbf3fc..5b23316cb90 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json index f154d6f5307..ba344ea3ce4 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json index 522b93a4c65..d7a371db853 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json index fd30a9fd149..eb1c40a7939 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index f40ebda9c30..345427444ea 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index 5fa4782bf02..2aa7846ca97 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.0", + "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", From d45ebcad1ce7eef35ccbba28b76053f35983e828 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Mon, 21 Oct 2024 18:17:58 +0200 Subject: [PATCH 034/372] chore(iast): add stringio read aspect test (#11055) --- tests/appsec/iast/aspects/test_stringio.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/appsec/iast/aspects/test_stringio.py diff --git a/tests/appsec/iast/aspects/test_stringio.py b/tests/appsec/iast/aspects/test_stringio.py new file mode 100644 index 00000000000..cab99ebe24b --- /dev/null +++ b/tests/appsec/iast/aspects/test_stringio.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +import pytest + +from ddtrace.appsec._common_module_patches import patch_common_modules +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import stringio_aspect +from tests.utils import override_global_config + + +def test_stringio_aspect_read(): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + tainted = taint_pyobject( + pyobject="foobar", + source_name="test_stringio_read_aspect_tainted_string", + source_value="foobar", + source_origin=OriginType.PARAMETER, + ) + sio = stringio_aspect(None, 0, tainted) + val = sio.read() + assert is_pyobject_tainted(val) + ranges = get_tainted_ranges(val) + assert len(ranges) == 1 + + +@pytest.mark.skip("TODO: APPSEC-55319") +def test_stringio_aspect_read_with_offset(): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + not_tainted = "foobazbazfoo" + tainted = taint_pyobject( + pyobject="foobar", + source_name="test_stringio_read_aspect_tainted_string", + source_value="foobar", + source_origin=OriginType.PARAMETER, + ) + added = add_aspect(not_tainted, tainted) + sio = stringio_aspect(None, 0, added) + val = sio.read(10) + # If the StringIO() and read() aspects were perfect, `val` would not be tainted + assert not is_pyobject_tainted(val) + ranges = get_tainted_ranges(val) + assert len(ranges) == 0 + + val_tainted = sio.read(5) + assert is_pyobject_tainted(val_tainted) + ranges = get_tainted_ranges(val_tainted) + assert len(ranges) == 1 From 523595554a84a9634a2a87f245f6c2c371e5e60b Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:10:39 -0400 Subject: [PATCH 035/372] feat(llmobs): make model name an optional argument for llm(), embedding() (#11051) This PR changes the `model_name` argument from `LLMObs.llm(), LLMObs.embedding(), @llmobs.decorators.llm, @llmobs.decorators.embedding` to being an optional argument with a default value of `custom`. Previously, being a required argument resulted in slight onboarding friction due to breaking if users did not pass in a model name. By being optional and defaulting to a placeholder value, the `llm/embedding()` methods should be easier to use and generate spans. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_llmobs.py | 19 ++++----- ddtrace/llmobs/decorators.py | 11 ++--- ...-model-name-optional-d916c96c7ace9375.yaml | 5 +++ tests/llmobs/test_llmobs_decorators.py | 40 ++++++++++++------- tests/llmobs/test_llmobs_service.py | 29 ++++++-------- 5 files changed, 57 insertions(+), 47 deletions(-) create mode 100644 releasenotes/notes/feat-llmobs-model-name-optional-d916c96c7ace9375.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 9e7d899aca0..42e27d33fb4 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -404,7 +404,7 @@ def _start_span( @classmethod def llm( cls, - model_name: str, + model_name: Optional[str] = None, name: Optional[str] = None, model_provider: Optional[str] = None, session_id: Optional[str] = None, @@ -413,7 +413,7 @@ def llm( """ Trace an invocation call to an LLM where inputs and outputs are represented as text. - :param str model_name: The name of the invoked LLM. + :param str model_name: The name of the invoked LLM. If not provided, a default value of "custom" will be set. :param str name: The name of the traced operation. If not provided, a default value of "llm" will be set. :param str model_provider: The name of the invoked LLM provider (ex: openai, bedrock). If not provided, a default value of "custom" will be set. @@ -425,12 +425,10 @@ def llm( """ if cls.enabled is False: log.warning(SPAN_START_WHILE_DISABLED_WARNING) - if not model_name: - log.warning("LLMObs.llm() missing model_name") + if model_name is None: + model_name = "custom" if model_provider is None: model_provider = "custom" - if model_name is None: - model_name = "unknown" return cls._instance._start_span( "llm", name, model_name=model_name, model_provider=model_provider, session_id=session_id, ml_app=ml_app ) @@ -504,7 +502,7 @@ def workflow( @classmethod def embedding( cls, - model_name: str, + model_name: Optional[str] = None, name: Optional[str] = None, model_provider: Optional[str] = None, session_id: Optional[str] = None, @@ -514,6 +512,7 @@ def embedding( Trace a call to an embedding model or function to create an embedding. :param str model_name: The name of the invoked embedding model. + If not provided, a default value of "custom" will be set. :param str name: The name of the traced operation. If not provided, a default value of "embedding" will be set. :param str model_provider: The name of the invoked LLM provider (ex: openai, bedrock). If not provided, a default value of "custom" will be set. @@ -525,12 +524,10 @@ def embedding( """ if cls.enabled is False: log.warning(SPAN_START_WHILE_DISABLED_WARNING) - if not model_name: - log.warning("LLMObs.embedding() missing model_name") + if model_name is None: + model_name = "custom" if model_provider is None: model_provider = "custom" - if model_name is None: - model_name = "unknown" return cls._instance._start_span( "embedding", name, diff --git a/ddtrace/llmobs/decorators.py b/ddtrace/llmobs/decorators.py index 7c6c8f61e87..86d8a1c2593 100644 --- a/ddtrace/llmobs/decorators.py +++ b/ddtrace/llmobs/decorators.py @@ -15,7 +15,8 @@ def _model_decorator(operation_kind): def decorator( - model_name: str, + original_func: Optional[Callable] = None, + model_name: Optional[str] = None, model_provider: Optional[str] = None, name: Optional[str] = None, session_id: Optional[str] = None, @@ -31,8 +32,7 @@ async def wrapper(*args, **kwargs): return await func(*args, **kwargs) traced_model_name = model_name if traced_model_name is None: - log.warning("model_name missing for LLMObs.%s() - default to 'unknown'", operation_kind) - traced_model_name = "unknown" + traced_model_name = "custom" span_name = name if span_name is None: span_name = func.__name__ @@ -55,8 +55,7 @@ def wrapper(*args, **kwargs): return func(*args, **kwargs) traced_model_name = model_name if traced_model_name is None: - log.warning("model_name missing for LLMObs.%s() - default to 'unknown'", operation_kind) - traced_model_name = "unknown" + traced_model_name = "custom" span_name = name if span_name is None: span_name = func.__name__ @@ -72,6 +71,8 @@ def wrapper(*args, **kwargs): return wrapper + if original_func and callable(original_func): + return inner(original_func) return inner return decorator diff --git a/releasenotes/notes/feat-llmobs-model-name-optional-d916c96c7ace9375.yaml b/releasenotes/notes/feat-llmobs-model-name-optional-d916c96c7ace9375.yaml new file mode 100644 index 00000000000..f36196e3091 --- /dev/null +++ b/releasenotes/notes/feat-llmobs-model-name-optional-d916c96c7ace9375.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + LLM Observability: When starting LLM and embedding spans, the ``model_name`` argument is now optional and will default to ``custom``. + This applies to both inline methods (e.g. ``LLMObs.llm()``) and function decorators (e.g. ``@llm``). diff --git a/tests/llmobs/test_llmobs_decorators.py b/tests/llmobs/test_llmobs_decorators.py index 5e8ce445f9c..82fede35ed2 100644 --- a/tests/llmobs/test_llmobs_decorators.py +++ b/tests/llmobs/test_llmobs_decorators.py @@ -69,23 +69,29 @@ def f(): ) -def test_llm_decorator_no_model_name_raises_error(LLMObs, mock_llmobs_span_writer): - with pytest.raises(TypeError): +def test_llm_decorator_no_model_name_sets_default(LLMObs, mock_llmobs_span_writer): + @llm(model_provider="test_provider", name="test_function", session_id="test_session_id") + def f(): + pass - @llm(model_provider="test_provider", name="test_function", session_id="test_session_id") - def f(): - pass + f() + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_llm_span_event( + span, "llm", model_name="custom", model_provider="test_provider", session_id="test_session_id" + ) + ) def test_llm_decorator_default_kwargs(LLMObs, mock_llmobs_span_writer): - @llm(model_name="test_model") + @llm def f(): pass f() span = LLMObs._instance.tracer.pop()[0] mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event(span, "llm", model_name="test_model", model_provider="custom") + _expected_llmobs_llm_span_event(span, "llm", model_name="custom", model_provider="custom") ) @@ -105,23 +111,29 @@ def f(): ) -def test_embedding_decorator_no_model_name_raises_error(LLMObs): - with pytest.raises(TypeError): +def test_embedding_decorator_no_model_name_sets_default(LLMObs, mock_llmobs_span_writer): + @embedding(model_provider="test_provider", name="test_function", session_id="test_session_id") + def f(): + pass - @embedding(model_provider="test_provider", name="test_function", session_id="test_session_id") - def f(): - pass + f() + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_llm_span_event( + span, "embedding", model_name="custom", model_provider="test_provider", session_id="test_session_id" + ) + ) def test_embedding_decorator_default_kwargs(LLMObs, mock_llmobs_span_writer): - @embedding(model_name="test_model") + @embedding def f(): pass f() span = LLMObs._instance.tracer.pop()[0] mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event(span, "embedding", model_name="test_model", model_provider="custom") + _expected_llmobs_llm_span_event(span, "embedding", model_name="custom", model_provider="custom") ) diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 8abcc920365..c69a0c36829 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -232,15 +232,13 @@ def test_llm_span_agentless(AgentlessLLMObs, mock_llmobs_span_agentless_writer): ) -def test_llm_span_no_model_raises_error(LLMObs, mock_logs): - with pytest.raises(TypeError): - with LLMObs.llm(name="test_llm_call", model_provider="test_provider"): - pass - +def test_llm_span_no_model_sets_default(LLMObs, mock_llmobs_span_writer): + with LLMObs.llm(name="test_llm_call", model_provider="test_provider") as span: + assert span.get_tag(MODEL_NAME) == "custom" -def test_llm_span_empty_model_name_logs_warning(LLMObs, mock_logs): - _ = LLMObs.llm(model_name="", name="test_llm_call", model_provider="test_provider") - mock_logs.warning.assert_called_once_with("LLMObs.llm() missing model_name") + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_llm_span_event(span, "llm", model_name="custom", model_provider="test_provider") + ) def test_default_model_provider_set_to_custom(LLMObs): @@ -325,15 +323,12 @@ def test_agent_span_agentless(AgentlessLLMObs, mock_llmobs_span_agentless_writer mock_llmobs_span_agentless_writer.enqueue.assert_called_with(_expected_llmobs_llm_span_event(span, "agent")) -def test_embedding_span_no_model_raises_error(LLMObs): - with pytest.raises(TypeError): - with LLMObs.embedding(name="test_embedding", model_provider="test_provider"): - pass - - -def test_embedding_span_empty_model_name_logs_warning(LLMObs, mock_logs): - _ = LLMObs.embedding(model_name="", name="test_embedding", model_provider="test_provider") - mock_logs.warning.assert_called_once_with("LLMObs.embedding() missing model_name") +def test_embedding_span_no_model_sets_default(LLMObs, mock_llmobs_span_writer): + with LLMObs.embedding(name="test_embedding", model_provider="test_provider") as span: + assert span.get_tag(MODEL_NAME) == "custom" + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_llm_span_event(span, "embedding", model_name="custom", model_provider="test_provider") + ) def test_embedding_default_model_provider_set_to_custom(LLMObs): From 74c45e4de673207c7e342fa27571108af0eb1c57 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:19:36 +0000 Subject: [PATCH 036/372] chore: update redis latest version to 5.1.1 (#10972) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/9232661.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.riot/requirements/9232661.txt b/.riot/requirements/9232661.txt index 65ab43360bc..f4ce839d3bb 100644 --- a/.riot/requirements/9232661.txt +++ b/.riot/requirements/9232661.txt @@ -17,5 +17,5 @@ pytest-asyncio==0.23.7 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -redis==5.1.0 +redis==5.1.1 sortedcontainers==2.4.0 From 7e8a914cda2fd33666eb1655ee36f8db40e0e9d2 Mon Sep 17 00:00:00 2001 From: Luc Vieillescazes Date: Tue, 22 Oct 2024 10:35:00 +0200 Subject: [PATCH 037/372] chore(onboarding): fix condition to enable SSI telemetry (#11101) We noticed we weren't getting any telemetry https://datadoghq.atlassian.net/browse/INPLAT-11 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- lib-injection/sources/sitecustomize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index c41967b03b5..2d6faa78cc0 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -33,7 +33,7 @@ def parse_version(version: str) -> Tuple: FORCE_INJECT = os.environ.get("DD_INJECT_FORCE", "").lower() in ("true", "1", "t") FORWARDER_EXECUTABLE = os.environ.get("DD_TELEMETRY_FORWARDER_PATH", "") -TELEMETRY_ENABLED = "true" in os.environ.get("DD_INJECTION_ENABLED", "").lower() +TELEMETRY_ENABLED = "DD_INJECTION_ENABLED" in os.environ DEBUG_MODE = os.environ.get("DD_TRACE_DEBUG", "").lower() in ("true", "1", "t") INSTALLED_PACKAGES = None PYTHON_VERSION = None From 50b335982edd0fc283efc8f416f7f4bb32548f04 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 22 Oct 2024 11:01:04 +0200 Subject: [PATCH 038/372] chore(iast): restore Match tests (#11107) IAST context refactor found a problem with tainted re.Match objects https://github.com/DataDog/dd-trace-py/pull/10988/files#diff-85e91d30afa95f5cf701f8187200d0bdcec50d55999ef03708d49fa5a5ccb1d6R127 It looks this PR fixed the problem https://github.com/DataDog/dd-trace-py/pull/11042 So, this PR enables again Match ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/_iast/_taint_tracking/__init__.py | 4 +- tests/appsec/iast/aspects/test_re_aspects.py | 8 +-- tests/contrib/flask/test_flask_appsec_iast.py | 56 ++++++++++++++++++- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 041159410f1..02ee7a00e1c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -261,8 +261,10 @@ def trace_calls_and_returns(frame, event, arg): def copy_ranges_to_string(s: str, ranges: Sequence[TaintRange]) -> str: + if not isinstance(s, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return s for r in ranges: - if s in r.source.value: + if r.source.value and s in r.source.value: s = _taint_pyobject_base( pyobject=s, source_name=r.source.name, source_value=r.source.value, source_origin=r.source.origin ) diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index d84251dcc88..c8dd93b97dc 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -24,9 +24,6 @@ from ddtrace.appsec._iast._taint_tracking.aspects import split_aspect -pytest.skip(reason="TAINTEABLE_TYPES Match contains errors. APPSEC-55239", allow_module_level=True) - - def test_re_findall_aspect_tainted_string(): tainted_foobarbaz = taint_pyobject( pyobject="/foo/bar/baaz.jpeg", @@ -581,8 +578,9 @@ def test_re_finditer_aspect_tainted_bytes(): res_iterator = re_finditer_aspect(None, 1, OPTION_RE, tainted_multipart) assert isinstance(res_iterator, typing.Iterator), f"res_iterator is of type {type(res_iterator)}" - for i in res_no_tainted: - assert i.group(0) == b'; filename="test.txt"' + res_list = list(res_no_tainted) + assert res_list[0].group(0) == b' name="files"' + assert res_list[1].group(0) == b'; filename="test.txt"' try: tainted_item = next(res_iterator) diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index f5b8774a140..fdb0bbb1af7 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -30,7 +30,8 @@ class FlaskAppSecIASTEnabledTestCase(BaseFlaskTestCase): @pytest.fixture(autouse=True) - def inject_fixtures(self, caplog): + def inject_fixtures(self, caplog, telemetry_writer): # noqa: F811 + self._telemetry_writer = telemetry_writer self._caplog = caplog def setUp(self): @@ -926,6 +927,59 @@ def iterate_json(data, parent_key=""): assert vulnerability["location"]["path"] == TEST_FILE_PATH assert vulnerability["hash"] == hash_value + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_request_body_iast_and_appsec(self): + """Verify IAST, Appsec and API security work correctly running at the same time""" + + @self.app.route("/sqli/body/", methods=("POST",)) + def sqli_10(): + import json + import sqlite3 + + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect + + con = sqlite3.connect(":memory:") + cur = con.cursor() + if flask_version > (2, 0): + json_data = request.json + else: + json_data = json.loads(request.data) + value = json_data.get("json_body") + assert value == "master" + + assert is_pyobject_tainted(value) + query = add_aspect(add_aspect("SELECT tbl_name FROM sqlite_", value), " WHERE tbl_name LIKE 'password'") + # label test_flask_request_body + cur.execute(query) + + return {"Response": value}, 200 + + with override_global_config( + dict( + _iast_enabled=True, + _asm_enabled=True, + _api_security_enabled=True, + _deduplication_enabled=False, + _iast_request_sampling=100.0, + ) + ): + resp = self.client.post( + "/sqli/body/", data=json.dumps(dict(json_body="master")), content_type="application/json" + ) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [{"name": "json_body", "origin": "http.request.body", "value": "master"}] + + list_metrics_logs = list(self._telemetry_writer._logs) + assert len(list_metrics_logs) == 0 + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_flask_full_sqli_iast_enabled_http_request_header_values_scrubbed(self): @self.app.route("/sqli//", methods=["GET", "POST"]) From 2abc58a2971610cf2625a1934ac54d3da3c9814d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:28:35 +0000 Subject: [PATCH 039/372] chore: update fastapi latest version to 0.114.0 (#10613) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Quinna Halim --- .riot/requirements/108f9cf.txt | 14 +++++++------- .riot/requirements/1322180.txt | 14 +++++++------- .riot/requirements/134c543.txt | 12 ++++++------ .riot/requirements/13f7667.txt | 8 ++++---- .riot/requirements/16759fe.txt | 4 ++-- .riot/requirements/1912989.txt | 12 ++++++------ .riot/requirements/1b7df87.txt | 4 ++-- .riot/requirements/1d3d3db.txt | 12 ++++++------ .riot/requirements/22fdd8b.txt | 8 ++++---- .riot/requirements/41b3223.txt | 14 +++++++------- .riot/requirements/5a5524a.txt | 4 ++-- .riot/requirements/71adece.txt | 16 ++++++++-------- .riot/requirements/ee3bb72.txt | 18 +++++++++--------- .riot/requirements/f709b80.txt | 10 +++++----- .riot/requirements/f823b38.txt | 10 +++++----- .riot/requirements/fa3a84d.txt | 12 ++++++------ 16 files changed, 86 insertions(+), 86 deletions(-) diff --git a/.riot/requirements/108f9cf.txt b/.riot/requirements/108f9cf.txt index c2d421da4eb..68e407c1fea 100644 --- a/.riot/requirements/108f9cf.txt +++ b/.riot/requirements/108f9cf.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/108f9cf.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/108f9cf.in # aiofiles==24.1.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -28,11 +28,11 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1322180.txt b/.riot/requirements/1322180.txt index 1b43d42e773..d7f77ff6d04 100644 --- a/.riot/requirements/1322180.txt +++ b/.riot/requirements/1322180.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1322180.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1322180.in # aiofiles==24.1.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -28,11 +28,11 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/134c543.txt b/.riot/requirements/134c543.txt index 722adf2c0d2..9b47784aff9 100644 --- a/.riot/requirements/134c543.txt +++ b/.riot/requirements/134c543.txt @@ -9,11 +9,11 @@ annotated-types==0.7.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -fastapi==0.115.0 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +fastapi==0.115.2 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,10 +29,10 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.38.6 +starlette==0.40.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/13f7667.txt b/.riot/requirements/13f7667.txt index 1dae0a3d594..cdb3642f7bf 100644 --- a/.riot/requirements/13f7667.txt +++ b/.riot/requirements/13f7667.txt @@ -8,11 +8,11 @@ aiofiles==24.1.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 fastapi==0.86.0 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -27,7 +27,7 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/16759fe.txt b/.riot/requirements/16759fe.txt index 073d79da76f..27b3dd8d79e 100644 --- a/.riot/requirements/16759fe.txt +++ b/.riot/requirements/16759fe.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/16759fe.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/16759fe.in # aiofiles==23.2.1 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 fastapi==0.90.1 diff --git a/.riot/requirements/1912989.txt b/.riot/requirements/1912989.txt index 3c83b0af957..3c74ad97a75 100644 --- a/.riot/requirements/1912989.txt +++ b/.riot/requirements/1912989.txt @@ -9,11 +9,11 @@ annotated-types==0.7.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -fastapi==0.115.0 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +fastapi==0.115.2 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,10 +29,10 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.38.6 +starlette==0.40.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1b7df87.txt b/.riot/requirements/1b7df87.txt index f61eb35d41c..b23492441b9 100644 --- a/.riot/requirements/1b7df87.txt +++ b/.riot/requirements/1b7df87.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1b7df87.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1b7df87.in # aiofiles==23.2.1 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 fastapi==0.64.0 diff --git a/.riot/requirements/1d3d3db.txt b/.riot/requirements/1d3d3db.txt index 19d8cfab596..8e4d720320e 100644 --- a/.riot/requirements/1d3d3db.txt +++ b/.riot/requirements/1d3d3db.txt @@ -5,15 +5,15 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d3d3db.in # aiofiles==24.1.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,12 +29,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/22fdd8b.txt b/.riot/requirements/22fdd8b.txt index 512d05464e4..c2ab379f101 100644 --- a/.riot/requirements/22fdd8b.txt +++ b/.riot/requirements/22fdd8b.txt @@ -8,11 +8,11 @@ aiofiles==24.1.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 fastapi==0.86.0 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -27,7 +27,7 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/41b3223.txt b/.riot/requirements/41b3223.txt index 0c3dfe6ea63..d97afcad0ae 100644 --- a/.riot/requirements/41b3223.txt +++ b/.riot/requirements/41b3223.txt @@ -6,15 +6,15 @@ # aiofiles==24.1.0 annotated-types==0.7.0 -anyio==4.5.0 +anyio==4.5.2 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 -fastapi==0.115.0 +fastapi==0.115.2 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -31,12 +31,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.0.1 +starlette==0.40.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/5a5524a.txt b/.riot/requirements/5a5524a.txt index f0cf775d84b..f4faac2d0ad 100644 --- a/.riot/requirements/5a5524a.txt +++ b/.riot/requirements/5a5524a.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/5a5524a.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/5a5524a.in # aiofiles==23.2.1 annotated-types==0.5.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 fastapi==0.103.2 diff --git a/.riot/requirements/71adece.txt b/.riot/requirements/71adece.txt index 68c55bb9fe8..3c7da46fac6 100644 --- a/.riot/requirements/71adece.txt +++ b/.riot/requirements/71adece.txt @@ -6,15 +6,15 @@ # aiofiles==24.1.0 annotated-types==0.7.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 -fastapi==0.115.0 +fastapi==0.115.2 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -31,12 +31,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.0.1 +starlette==0.40.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/ee3bb72.txt b/.riot/requirements/ee3bb72.txt index 673bf6396f5..9ba70beec7b 100644 --- a/.riot/requirements/ee3bb72.txt +++ b/.riot/requirements/ee3bb72.txt @@ -2,19 +2,19 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/ee3bb72.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ee3bb72.in # aiofiles==24.1.0 annotated-types==0.7.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 -fastapi==0.115.0 +fastapi==0.115.2 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -30,11 +30,11 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.0.1 +starlette==0.40.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/f709b80.txt b/.riot/requirements/f709b80.txt index 0996cba9741..4d8eec013bb 100644 --- a/.riot/requirements/f709b80.txt +++ b/.riot/requirements/f709b80.txt @@ -5,15 +5,15 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/f709b80.in # aiofiles==24.1.0 -anyio==4.5.0 +anyio==4.5.2 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,12 +29,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/f823b38.txt b/.riot/requirements/f823b38.txt index 3df256accdf..99549b8b77f 100644 --- a/.riot/requirements/f823b38.txt +++ b/.riot/requirements/f823b38.txt @@ -5,15 +5,15 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/f823b38.in # aiofiles==24.1.0 -anyio==4.5.0 +anyio==4.5.2 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,12 +29,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/fa3a84d.txt b/.riot/requirements/fa3a84d.txt index b2364bb7b7e..c8259b6ba0d 100644 --- a/.riot/requirements/fa3a84d.txt +++ b/.riot/requirements/fa3a84d.txt @@ -5,15 +5,15 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/fa3a84d.in # aiofiles==24.1.0 -anyio==4.6.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.6 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -29,12 +29,12 @@ pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.10 +python-multipart==0.0.12 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 From dacfb8d3d202a8a187caf7c3dd2cd813f77c41a1 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:02:22 +0200 Subject: [PATCH 040/372] chore(asm): finalize refactor (#11110) Following https://github.com/DataDog/dd-trace-py/pull/10899 and https://github.com/DataDog/dd-trace-py/pull/11012, this PR removes the last traces of the previous instrumentation for ASM. APPSEC-55053 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_asm_request_context.py | 3 --- ddtrace/appsec/_handlers.py | 2 -- ddtrace/contrib/internal/asgi/middleware.py | 4 ++-- tests/appsec/appsec/test_appsec_trace_utils.py | 4 ++-- tests/appsec/appsec/test_processor.py | 13 +++++++------ tests/appsec/utils.py | 7 ++++++- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 7441a335ed5..7b266e5a4c2 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -16,7 +16,6 @@ from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import SPAN_DATA_NAMES -from ddtrace.appsec._constants import WAF_CONTEXT_NAMES from ddtrace.appsec._utils import get_triggers from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException @@ -161,8 +160,6 @@ def set_blocked(blocked: Dict[str, Any]) -> None: return _ctype_from_headers(blocked, get_headers()) env.blocked = blocked - # DEV: legacy code, to be removed - core.set_item(WAF_CONTEXT_NAMES.BLOCKED, True, span=env.span) def update_span_metrics(span: Span, name: str, value: Union[float, int]) -> None: diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index 4a3234ae7bd..42464f9fe40 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -11,7 +11,6 @@ from ddtrace.ext import SpanTypes from ddtrace.ext import http from ddtrace.internal import core -from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED from ddtrace.internal.constants import RESPONSE_HEADERS from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import http as http_utils @@ -276,7 +275,6 @@ def _asgi_make_block_content(ctx, url): def _on_flask_blocked_request(span): - core.set_item(HTTP_REQUEST_BLOCKED, True) span.set_tag_str(http.STATUS_CODE, "403") request = core.get_item("flask_request") try: diff --git a/ddtrace/contrib/internal/asgi/middleware.py b/ddtrace/contrib/internal/asgi/middleware.py index ac2f61ecf26..e32d2994a3d 100644 --- a/ddtrace/contrib/internal/asgi/middleware.py +++ b/ddtrace/contrib/internal/asgi/middleware.py @@ -19,11 +19,11 @@ from ddtrace.internal._exceptions import BlockingException from ddtrace.internal.compat import is_valid_ip from ddtrace.internal.constants import COMPONENT -from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.internal.utils import get_blocked +from ddtrace.internal.utils import set_blocked log = get_logger(__name__) @@ -301,7 +301,7 @@ async def wrapped_blocked_send(message): # Do not block right here. Wait for route to be resolved in starlette/patch.py return await self.app(scope, receive, wrapped_send) except BlockingException as e: - core.set_item(HTTP_REQUEST_BLOCKED, e.args[0]) + set_blocked(e.args[0]) return await _blocked_asgi_app(scope, receive, wrapped_blocked_send) except Exception as exc: (exc_type, exc_val, exc_tb) = sys.exc_info() diff --git a/tests/appsec/appsec/test_appsec_trace_utils.py b/tests/appsec/appsec/test_appsec_trace_utils.py index 782f0e9869c..b550b103782 100644 --- a/tests/appsec/appsec/test_appsec_trace_utils.py +++ b/tests/appsec/appsec/test_appsec_trace_utils.py @@ -13,9 +13,9 @@ from ddtrace.appsec.trace_utils import track_user_signup_event from ddtrace.contrib.trace_utils import set_user from ddtrace.ext import user -from ddtrace.internal import core import tests.appsec.rules as rules from tests.appsec.utils import asm_context +from tests.appsec.utils import is_blocked from tests.utils import TracerTestCase @@ -235,7 +235,7 @@ def test_set_user_blocked(self): assert span.get_tag(user.ROLE) assert span.get_tag(user.SCOPE) assert span.get_tag(user.SESSION_ID) - assert core.get_item("http.request.blocked", span=span) + assert is_blocked(span) def test_no_span_doesnt_raise(self): from ddtrace import tracer diff --git a/tests/appsec/appsec/test_processor.py b/tests/appsec/appsec/test_processor.py index cbf9ad296fc..c9f6e2e2d3a 100644 --- a/tests/appsec/appsec/test_processor.py +++ b/tests/appsec/appsec/test_processor.py @@ -19,6 +19,7 @@ from ddtrace.internal import core import tests.appsec.rules as rules from tests.appsec.utils import asm_context +from tests.appsec.utils import is_blocked from tests.utils import override_env from tests.utils import override_global_config from tests.utils import snapshot @@ -204,7 +205,7 @@ def test_ip_block(tracer): ) assert get_triggers(span) assert core.get_item("http.request.remote_ip", span) == rules._IP.BLOCKED - assert core.get_item("http.request.blocked", span) + assert is_blocked(span) @pytest.mark.parametrize("ip", [rules._IP.MONITORED, rules._IP.BYPASS, rules._IP.DEFAULT]) @@ -216,11 +217,11 @@ def test_ip_not_block(tracer, ip): ) assert core.get_item("http.request.remote_ip", span) == ip - assert core.get_item("http.request.blocked", span) is None + assert is_blocked(span) is False def test_ip_update_rules_and_block(tracer): - with asm_context(tracer=tracer, ip_addr=rules._IP.BLOCKED, config=config_asm): + with asm_context(tracer=tracer, ip_addr=rules._IP.BLOCKED, config=config_asm) as span1: tracer._appsec_processor._update_rules( { "rules_data": [ @@ -240,8 +241,8 @@ def test_ip_update_rules_and_block(tracer): rules.Config(), ) - assert core.get_item("http.request.remote_ip", span) == rules._IP.BLOCKED - assert core.get_item("http.request.blocked", span) + assert core.get_item("http.request.remote_ip", span1) == rules._IP.BLOCKED + assert is_blocked(span1) def test_ip_update_rules_expired_no_block(tracer): @@ -266,7 +267,7 @@ def test_ip_update_rules_expired_no_block(tracer): ) assert core.get_item("http.request.remote_ip", span) == rules._IP.BLOCKED - assert core.get_item("http.request.blocked", span) is None + assert is_blocked(span) is False @snapshot( diff --git a/tests/appsec/utils.py b/tests/appsec/utils.py index b4c010d1123..9e09b3ae2ad 100644 --- a/tests/appsec/utils.py +++ b/tests/appsec/utils.py @@ -3,6 +3,7 @@ import typing from ddtrace import tracer as default_tracer +from ddtrace._trace.span import Span from ddtrace.ext import SpanTypes import ddtrace.internal.core as core from ddtrace.settings.asm import config as asm_config @@ -30,7 +31,7 @@ def asm_context( block_request_callable: typing.Optional[typing.Callable[[], bool]] = None, service: typing.Optional[str] = None, config=None, -): +) -> typing.Iterator[Span]: with override_global_config(config) if config else contextlib.nullcontext(): if tracer is None: tracer = default_tracer @@ -48,3 +49,7 @@ def asm_context( service=service, ), tracer.trace(span_name or "test", span_type=SpanTypes.WEB, service=service) as span: yield span + + +def is_blocked(span: Span) -> bool: + return span.get_tag("appsec.blocked") == "true" and span.get_tag("appsec.event") == "true" From 7eb89039a5a341558e43c5c742eba7304b70e0e0 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 22 Oct 2024 17:01:59 +0200 Subject: [PATCH 041/372] chore(iast): disable test packages tests (#11096) Disable test packages tests due to [they are failing in main](https://app.circleci.com/pipelines/github/DataDog/dd-trace-py?branch=main) I enabled this step in this branch and it works now: https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/75403/workflows/c449e532-b91a-4dc4-a1d9-840aab89de3d/jobs/4336359 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/iast_packages/test_packages.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index aa9507a4241..fae40bf89e5 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -218,6 +218,7 @@ def uninstall(self, python_cmd): import_name="charset_normalizer", import_module_to_validate="charset_normalizer.api", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting("click", "8.1.7", "", "Hello World!\nHello World!\n", "", import_module_to_validate="click.core"), PackageForTesting( @@ -289,6 +290,7 @@ def uninstall(self, python_cmd): "xn--eckwd4c7c.xn--zckzah", import_module_to_validate="idna.codec", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "importlib-resources", @@ -477,6 +479,7 @@ def uninstall(self, python_cmd): "", import_module_to_validate="multipart.multipart", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "pytz", @@ -511,6 +514,7 @@ def uninstall(self, python_cmd): "", import_module_to_validate="rsa.pkcs1", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "sqlalchemy", @@ -590,6 +594,7 @@ def uninstall(self, python_cmd): extras=[("beautifulsoup4", "4.12.3")], skip_python_version=[(3, 6), (3, 7), (3, 8)], test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "werkzeug", @@ -610,6 +615,7 @@ def uninstall(self, python_cmd): import_module_to_validate="yarl._url", skip_python_version=[(3, 6), (3, 7), (3, 8)], test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "zipp", @@ -682,6 +688,7 @@ def uninstall(self, python_cmd): "", skip_python_version=[(3, 8)], test_propagation=True, + fixme_propagation_fails=True, ), ## TODO: https://datadoghq.atlassian.net/browse/APPSEC-53659 ## Disabled due to a bug in CI: @@ -761,6 +768,7 @@ def uninstall(self, python_cmd): '('Hello, world!')\n\n', "", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting("grpcio", "1.64.0", "", "", "", test_e2e=False, import_name="grpc"), PackageForTesting( From e86434e57e164907bf95af10837d3a27ba9de28d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:31:31 +0000 Subject: [PATCH 042/372] chore: update anthropic latest version to 0.36.0 (#10996) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Quinna Halim --- .riot/requirements/11031a2.txt | 39 ++++++++++++++++---------------- .riot/requirements/13e5e9e.txt | 39 ++++++++++++++++---------------- .riot/requirements/149b454.txt | 41 +++++++++++++++++----------------- .riot/requirements/160ef02.txt | 41 +++++++++++++++++----------------- .riot/requirements/1d5589b.txt | 41 +++++++++++++++++----------------- .riot/requirements/1d8be99.txt | 39 ++++++++++++++++---------------- .riot/requirements/1e0e8e5.txt | 41 +++++++++++++++++----------------- .riot/requirements/3c4b933.txt | 39 ++++++++++++++++---------------- .riot/requirements/6bff0b2.txt | 39 ++++++++++++++++---------------- .riot/requirements/87ce77a.txt | 39 ++++++++++++++++---------------- 10 files changed, 204 insertions(+), 194 deletions(-) diff --git a/.riot/requirements/11031a2.txt b/.riot/requirements/11031a2.txt index beaebeaadaf..14260069939 100644 --- a/.riot/requirements/11031a2.txt +++ b/.riot/requirements/11031a2.txt @@ -5,31 +5,32 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/11031a2.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -40,7 +41,7 @@ sortedcontainers==2.4.0 tokenizers==0.20.0 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/13e5e9e.txt b/.riot/requirements/13e5e9e.txt index 38540e0c5b7..6ca5e468b07 100644 --- a/.riot/requirements/13e5e9e.txt +++ b/.riot/requirements/13e5e9e.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/13e5e9e.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.5.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==1.26.19 -vcrpy==6.0.1 +urllib3==1.26.20 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/149b454.txt b/.riot/requirements/149b454.txt index 81a713e775d..2f2f5ea025f 100644 --- a/.riot/requirements/149b454.txt +++ b/.riot/requirements/149b454.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/149b454.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==1.26.19 -vcrpy==6.0.1 +urllib3==1.26.20 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/160ef02.txt b/.riot/requirements/160ef02.txt index 694360dfd53..001d922d7b7 100644 --- a/.riot/requirements/160ef02.txt +++ b/.riot/requirements/160ef02.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/160ef02.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==1.26.19 -vcrpy==6.0.1 +urllib3==1.26.20 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/1d5589b.txt b/.riot/requirements/1d5589b.txt index 045ce8c055a..c07e6fd20ea 100644 --- a/.riot/requirements/1d5589b.txt +++ b/.riot/requirements/1d5589b.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d5589b.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/1d8be99.txt b/.riot/requirements/1d8be99.txt index 51e021902ca..7a93a1e8f0a 100644 --- a/.riot/requirements/1d8be99.txt +++ b/.riot/requirements/1d8be99.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d8be99.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.5.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==1.26.19 -vcrpy==6.0.1 +urllib3==1.26.20 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/1e0e8e5.txt b/.riot/requirements/1e0e8e5.txt index 0f53bfb3420..cf34af1a67e 100644 --- a/.riot/requirements/1e0e8e5.txt +++ b/.riot/requirements/1e0e8e5.txt @@ -5,32 +5,33 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1e0e8e5.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -39,10 +40,10 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 tokenizers==0.20.0 -tomli==2.0.1 +tomli==2.0.2 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/3c4b933.txt b/.riot/requirements/3c4b933.txt index 01719ea8c85..b0f6120b9d1 100644 --- a/.riot/requirements/3c4b933.txt +++ b/.riot/requirements/3c4b933.txt @@ -5,31 +5,32 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/3c4b933.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -40,7 +41,7 @@ sortedcontainers==2.4.0 tokenizers==0.20.0 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/6bff0b2.txt b/.riot/requirements/6bff0b2.txt index 911d69f1062..7584df87cf3 100644 --- a/.riot/requirements/6bff0b2.txt +++ b/.riot/requirements/6bff0b2.txt @@ -5,31 +5,32 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/6bff0b2.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -40,7 +41,7 @@ sortedcontainers==2.4.0 tokenizers==0.20.0 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 diff --git a/.riot/requirements/87ce77a.txt b/.riot/requirements/87ce77a.txt index c5a9f1245e4..1fa837a1c56 100644 --- a/.riot/requirements/87ce77a.txt +++ b/.riot/requirements/87ce77a.txt @@ -5,31 +5,32 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/87ce77a.in # annotated-types==0.7.0 -anthropic==0.34.1 -anyio==4.4.0 +anthropic==0.36.0 +anyio==4.6.0 attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.2 distro==1.9.0 -filelock==3.15.4 -fsspec==2024.6.1 +filelock==3.16.1 +fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 -huggingface-hub==0.24.6 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.25.2 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.6.1 mock==5.1.0 -multidict==6.0.5 +multidict==6.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pytest==8.3.2 +propcache==0.2.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -40,7 +41,7 @@ sortedcontainers==2.4.0 tokenizers==0.20.0 tqdm==4.66.5 typing-extensions==4.12.2 -urllib3==2.2.2 -vcrpy==6.0.1 +urllib3==2.2.3 +vcrpy==6.0.2 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.14.0 From f5314ed0c18b7a663039356d098c77f259d636ac Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 22 Oct 2024 20:54:36 +0200 Subject: [PATCH 043/372] chore(iast): propagation error in replace aspect (#11120) Fix a propagation error in the replace aspect detected in the log explorer ``` File "/usr/local/lib/python3.12/site-packages/django/core/handlers/wsgi.py", line 67, in __init__ self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1)) File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_iast/_taint_tracking/aspects.py", line 816, in replace_aspect iast_taint_log_error("replace_aspect. {}".format(e)) File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_iast/_taint_tracking/__init__.py", line 129, in iast_taint_log_error _set_iast_error_metric("[IAST] Propagation error. %s" % msg) File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_deduplications.py", line 44, in __call__ result = self.func(*args, **kwargs) File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_iast/_metrics.py", line 70, in _set_iast_error_metric res.extend(traceback.format_stack(limit=20)) File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_iast/_taint_tracking/aspects.py", line 809, in replace_aspect aspect_result = aspect_replace_api(candidate_text, old_value, new_value, count, orig_result) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/ddtrace/appsec/_iast/_taint_tracking/aspects.py", line 763, in aspect_replace_api result_formatted = as_formatted_evidence(new_value, tag_mapping_function=TagMappingMode.Mapper).join(new_elements) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: sequence item 0: expected str instance, NoneType found ``` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/_iast/_taint_tracking/aspects.py | 2 ++ .../iast/aspects/test_replace_aspect.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 43f48f5baf7..732f90ceac0 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -759,6 +759,8 @@ def aspect_replace_api( len(old_value), candidate_text_ranges, ) + else: + new_elements = [element for element in new_elements if element is not None] result_formatted = as_formatted_evidence(new_value, tag_mapping_function=TagMappingMode.Mapper).join(new_elements) diff --git a/tests/appsec/iast/aspects/test_replace_aspect.py b/tests/appsec/iast/aspects/test_replace_aspect.py index ea27dc03b89..b30fa7cdede 100644 --- a/tests/appsec/iast/aspects/test_replace_aspect.py +++ b/tests/appsec/iast/aspects/test_replace_aspect.py @@ -683,6 +683,31 @@ def test_replace_tainted_results_in_no_tainted(origstr, substr, replstr, maxcoun assert is_pyobject_tainted(replaced) is False +@pytest.mark.parametrize( + "origstr,formatted", + [ + ("/", ""), + ("", ""), + ("", ""), + ("/waf/", "waf/"), + ("waf/", "waf"), + ("//waf/", "/waf/"), + ("path/waf/", "pathwaf/"), + ("a/:", "a:"), + # TODO: this replace raises basic_string::substr: __pos (which is 4) > this->size() (which is 3) + # ("a/:+-/", ":+-a-+::+-a/-+:"), + ], +) +def test_replace_tainted_results_in_no_tainted_django(origstr, formatted): + path_info = origstr.encode("iso-8859-1").decode() + sep = taint_pyobject(pyobject="/", source_name="d", source_value="/", source_origin=OriginType.PARAMETER) + replaced = ddtrace_aspects.replace_aspect(path_info.replace, 1, path_info, sep, "", 1) + assert replaced == path_info.replace(sep, "", 1) + + assert as_formatted_evidence(replaced) == formatted + assert is_pyobject_tainted(replaced) is False + + @pytest.mark.parametrize( "origstr, substr, replstr, maxcount, formatted", [ From a04689875b71cb99acb38a2a4c07cbaeb3bc6270 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 22 Oct 2024 16:59:57 -0400 Subject: [PATCH 044/372] chore(telemetry): fix flaky tests part 2 (#11052) Improves the reliability of instrumentation telemetry tests by: - Providing a mechanism to filter out telemetry events with unexpected runtime IDs in TelemetryTestSession.get_events(..). This enhances control over which telemetry events are captured in tests - Fixing the flaky `test_update_dependencies_event_when_disabled` by ensuring DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED=False is set. Previously, this test was run with default environment variables, which introduced flakiness. - Fixing the flaky `test_unhandled_integration_error` by avoiding assertions on the runtime ID of captured telemetry events. This assertion was a significant source of flakiness and was the main reason the subprocess parameter was added to TelemetryTestSession.get_events(..). A little unrelated but this PR also refactors the `test_app_started_event_configuration_override` to use _DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED instead of manually starting the telemetry writer. - Note: Importing from ddtrace.internal.telemetry previously created the symbol_db and dynamic_instrumentation objects as a side effect. Since we have removed this side effect, we must create these configurations in the test. - I can move this to another PR but this seems like a minor test change that's not worth the effort. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/conftest.py | 7 +++++- tests/integration/test_settings.py | 10 ++++---- tests/runtime/test_runtime_metrics_api.py | 4 +-- tests/telemetry/test_telemetry.py | 30 ++++++++--------------- tests/telemetry/test_writer.py | 25 +++++++++---------- 5 files changed, 35 insertions(+), 41 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2c99e7a9e4e..20995b7b6cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,6 +29,7 @@ from ddtrace.internal.compat import parse from ddtrace.internal.remoteconfig.client import RemoteConfigClient from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.runtime import get_runtime_id from ddtrace.internal.service import ServiceStatus from ddtrace.internal.service import ServiceStatusError from ddtrace.internal.telemetry import TelemetryWriter @@ -550,12 +551,16 @@ def get_requests(self, request_type=None, filter_heartbeats=True): return sorted(requests, key=lambda r: r["body"]["seq_id"], reverse=True) - def get_events(self, event_type=None, filter_heartbeats=True): + def get_events(self, event_type=None, filter_heartbeats=True, subprocess=False): """Get a list of the event payloads sent to the test agent Results are in reverse order by ``seq_id`` """ requests = self.get_requests(event_type, filter_heartbeats) + if subprocess: + # Use get_runtime_id to filter telemetry events generated in the current process + runtime_id = get_runtime_id() + requests = [req for req in requests if req["body"]["runtime_id"] != runtime_id] return [req["body"] for req in requests] def get_metrics(self, name=None): diff --git a/tests/integration/test_settings.py b/tests/integration/test_settings.py index 3b7e7d288b0..55e8d1e76d8 100644 --- a/tests/integration/test_settings.py +++ b/tests/integration/test_settings.py @@ -38,7 +38,7 @@ def test_setting_origin_environment(test_agent_session, run_python_code_in_subpr ) assert status == 0, err - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert { @@ -95,7 +95,7 @@ def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess): ) assert status == 0, err - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert { "name": "DD_TRACE_SAMPLE_RATE", @@ -173,7 +173,7 @@ def test_remoteconfig_sampling_rate_default(test_agent_session, run_python_code_ ) assert status == 0, err - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert {"name": "DD_TRACE_SAMPLE_RATE", "value": 1.0, "origin": "default"} in events_trace_sample_rate @@ -200,7 +200,7 @@ def test_remoteconfig_sampling_rate_telemetry(test_agent_session, run_python_cod ) assert status == 0, err - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) events_trace_sample_rate = _get_telemetry_config_items(events, "DD_TRACE_SAMPLE_RATE") assert {"name": "DD_TRACE_SAMPLE_RATE", "value": 0.5, "origin": "remote_config"} in events_trace_sample_rate @@ -237,7 +237,7 @@ def test_remoteconfig_header_tags_telemetry(test_agent_session, run_python_code_ ) assert status == 0, err - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) events_trace_header_tags = _get_telemetry_config_items(events, "DD_TRACE_HEADER_TAGS") assert { "name": "DD_TRACE_HEADER_TAGS", diff --git a/tests/runtime/test_runtime_metrics_api.py b/tests/runtime/test_runtime_metrics_api.py index 5e78a2f08d0..30431ff644c 100644 --- a/tests/runtime/test_runtime_metrics_api.py +++ b/tests/runtime/test_runtime_metrics_api.py @@ -71,7 +71,7 @@ def find_telemetry_event(events, request_type): _, stderr, status, _ = run_python_code_in_subprocess(code) assert status == 0, stderr - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) # app-started, app-closing, app-client-configuration-change, app-dependencies-loaded assert len(events) == 4 @@ -127,7 +127,7 @@ def find_telemetry_event(events, request_type): _, stderr, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env) assert status == 0, stderr - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) # app-started, app-closing, app-client-configuration-change, app-integrations-change, app-dependencies-loaded assert len(events) == 5 diff --git a/tests/telemetry/test_telemetry.py b/tests/telemetry/test_telemetry.py index 509017203d7..24dbaae45d0 100644 --- a/tests/telemetry/test_telemetry.py +++ b/tests/telemetry/test_telemetry.py @@ -55,12 +55,12 @@ def test_enable_fork(test_agent_session, run_python_code_in_subprocess): runtime_id = stdout.strip().decode("utf-8") # Validate that one app-closing event was sent and it was queued in the parent process - app_closing = test_agent_session.get_events("app-closing") + app_closing = test_agent_session.get_events("app-closing", subprocess=True) assert len(app_closing) == 1 assert app_closing[0]["runtime_id"] == runtime_id # Validate that one app-started event was sent and it was queued in the parent process - app_started = test_agent_session.get_events("app-started") + app_started = test_agent_session.get_events("app-started", subprocess=True) assert len(app_started) == 1 assert app_started[0]["runtime_id"] == runtime_id @@ -97,7 +97,7 @@ def test_enable_fork_heartbeat(test_agent_session, run_python_code_in_subprocess runtime_id = stdout.strip().decode("utf-8") # Allow test agent session to capture all heartbeat events - app_heartbeats = test_agent_session.get_events("app-heartbeat", filter_heartbeats=False) + app_heartbeats = test_agent_session.get_events("app-heartbeat", filter_heartbeats=False, subprocess=True) assert len(app_heartbeats) > 0 for hb in app_heartbeats: assert hb["runtime_id"] == runtime_id @@ -177,7 +177,7 @@ def process_trace(self, trace): assert status == 0, stderr assert b"Exception raised in trace filter" in stderr - events = test_agent_session.get_events("app-started") + events = test_agent_session.get_events("app-started", subprocess=True) assert len(events) == 1 @@ -221,7 +221,7 @@ def pre_ddtrace_exc_hook(exctype, value, traceback): # Regression test for invalid number of arguments in wrapped exception hook assert b"3 positional arguments but 4 were given" not in stderr - app_starteds = test_agent_session.get_events("app-started") + app_starteds = test_agent_session.get_events("app-started", subprocess=True) assert len(app_starteds) == 1 # app-started captures unhandled exceptions raised in application code assert app_starteds[0]["payload"]["error"]["code"] == 1 @@ -252,7 +252,7 @@ def test_handled_integration_error(test_agent_session, run_python_code_in_subpro expected_stderr = b"failed to import" assert expected_stderr in stderr - integrations_events = test_agent_session.get_events("app-integrations-change") + integrations_events = test_agent_session.get_events("app-integrations-change", subprocess=True) assert len(integrations_events) == 1 assert ( integrations_events[0]["payload"]["integrations"][0]["error"] @@ -270,9 +270,6 @@ def test_handled_integration_error(test_agent_session, run_python_code_in_subpro def test_unhandled_integration_error(test_agent_session, ddtrace_run_python_code_in_subprocess): code = """ -import logging -logging.basicConfig() - import flask f = flask.Flask("hi") @@ -286,20 +283,13 @@ def test_unhandled_integration_error(test_agent_session, ddtrace_run_python_code assert b"not enough values to unpack (expected 2, got 0)" in stderr, stderr - events = test_agent_session.get_events() - assert len(events) > 0 - # Same runtime id is used - first_runtimeid = events[0]["runtime_id"] - for event in events: - assert event["runtime_id"] == first_runtimeid - - app_started_event = [event for event in events if event["request_type"] == "app-started"] + app_started_event = test_agent_session.get_events("app-started", subprocess=True) assert len(app_started_event) == 1 assert app_started_event[0]["payload"]["error"]["code"] == 1 assert "ddtrace/contrib/internal/flask/patch.py" in app_started_event[0]["payload"]["error"]["message"] assert "not enough values to unpack (expected 2, got 0)" in app_started_event[0]["payload"]["error"]["message"] - integration_events = [event for event in events if event["request_type"] == "app-integrations-change"] + integration_events = test_agent_session.get_events("app-integrations-change", subprocess=True) integrations = integration_events[0]["payload"]["integrations"] (flask_integration,) = [integration for integration in integrations if integration["name"] == "flask"] @@ -332,7 +322,7 @@ def test_app_started_with_install_metrics(test_agent_session, run_python_code_in _, stderr, status, _ = run_python_code_in_subprocess("import ddtrace", env=env) assert status == 0, stderr - app_started_event = test_agent_session.get_events("app-started") + app_started_event = test_agent_session.get_events("app-started", subprocess=True) assert len(app_started_event) == 1 assert app_started_event[0]["payload"]["install_signature"] == { "install_id": "68e75c48-57ca-4a12-adfc-575c4b05fcbe", @@ -355,7 +345,7 @@ def test_instrumentation_telemetry_disabled(test_agent_session, run_python_code_ """ _, stderr, status, _ = run_python_code_in_subprocess(code, env=env) - events = test_agent_session.get_events() + events = test_agent_session.get_events(subprocess=True) assert len(events) == 0 assert status == 0, stderr diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index d90a7351eba..bcac256348f 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -204,16 +204,12 @@ def test_app_started_event_configuration_override(test_agent_session, run_python which is then sent by periodic() """ code = """ -import logging -logging.basicConfig() - +# most configurations are reported when ddtrace.auto is imported import ddtrace.auto - -# By default telemetry collection is enabled after 10 seconds, so we either need to -# to sleep for 10 seconds or manually call _app_started() to generate the app started event. -# This delay allows us to collect start up errors and dynamic configurations -import ddtrace -ddtrace.internal.telemetry.telemetry_writer._app_started() +# report configurations not used by ddtrace.auto +import ddtrace.settings.symbol_db +import ddtrace.settings.dynamic_instrumentation +import ddtrace.settings.exception_replay """ env = os.environ.copy() @@ -271,6 +267,10 @@ def test_app_started_event_configuration_override(test_agent_session, run_python env["DD_TRACE_PARTIAL_FLUSH_ENABLED"] = "false" env["DD_TRACE_PARTIAL_FLUSH_MIN_SPANS"] = "3" env["DD_SITE"] = "datadoghq.com" + # By default telemetry collection is enabled after 10 seconds, so we either need to + # to sleep for 10 seconds or manually call _app_started() to generate the app started event. + # This delay allows us to collect start up errors and dynamic configurations + env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true" _, stderr, status, _ = run_python_code_in_subprocess(code, env=env) assert status == 0, stderr @@ -398,7 +398,6 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_PROFILING__FORCE_LEGACY_EXPORTER", "origin": "env_var", "value": True}, {"name": "DD_REMOTE_CONFIGURATION_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "origin": "env_var", "value": 1.0}, - {"name": "DD_RUNTIME_METRICS_ENABLED", "origin": "unknown", "value": True}, {"name": "DD_RUNTIME_METRICS_ENABLED", "origin": "unknown", "value": False}, {"name": "DD_SERVICE", "origin": "default", "value": "unnamed-python-service"}, {"name": "DD_SERVICE_MAPPING", "origin": "env_var", "value": "default_dd_service:remapped_dd_service"}, @@ -495,8 +494,8 @@ def test_update_dependencies_event_when_disabled(test_agent_session, ddtrace_run # Import httppretty after ddtrace is imported, this ensures that the module is sent in a dependencies event # Imports httpretty twice and ensures only one dependency entry is sent - _, stderr, status, _ = ddtrace_run_python_code_in_subprocess("import xmltodict") - events = test_agent_session.get_events("app-dependencies-loaded") + _, stderr, status, _ = ddtrace_run_python_code_in_subprocess("import xmltodict", env=env) + events = test_agent_session.get_events("app-dependencies-loaded", subprocess=True) assert len(events) == 0, events @@ -647,7 +646,7 @@ def test_app_heartbeat_event_periodic(mock_time, telemetry_writer, test_agent_se # Assert next flush contains app-heartbeat event for _ in range(telemetry_writer._periodic_threshold): telemetry_writer.periodic() - assert test_agent_session.get_events("app-heartbeat") == [] + assert test_agent_session.get_events("app-heartbeat", filter_heartbeats=False) == [] telemetry_writer.periodic() heartbeat_events = test_agent_session.get_events("app-heartbeat", filter_heartbeats=False) From b8cda4ddc871a206e43a8601b8687ccc4b1232ce Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 23 Oct 2024 14:06:24 +0200 Subject: [PATCH 045/372] chore(ci): iast, reduce the number iterations in test_aggregated_leaks (#11126) reduce the number iterations in `test_aggregated_leaks` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py index 5749c7e7f64..4980259a3a6 100644 --- a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py +++ b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py @@ -8,5 +8,5 @@ async def test_aggregated_leaks(): with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): from scripts.iast.leak_functions import iast_leaks - result = await iast_leaks(75000, 1.0, 100) == 0 + result = await iast_leaks(60000, 0.2, 500) == 0 assert result From 9365a6ae31f3ba432cfd87b4eccdec9f5e09163d Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 23 Oct 2024 16:01:36 +0200 Subject: [PATCH 046/372] chore(iast): update header injection exclusions (#11111) Added new exclusions for header injection: "location", "pragma", "access-control-allow-", "sec-websocket-location", "sec-websocket-accept", "connection" ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_iast/taint_sinks/header_injection.py | 25 ++++--- tests/contrib/flask/test_flask_appsec_iast.py | 66 ++++++++++++++++--- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index cc091bb6ae1..4d56986c2d0 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -21,6 +21,21 @@ log = get_logger(__name__) +HEADER_INJECTION_EXCLUSIONS = { + "location", + "pragma", + "content-type", + "content-length", + "content-encoding", + "transfer-encoding", + "set-cookie", + "vary", + "access-control-allow-", + "sec-websocket-location", + "sec-websocket-accept", + "connection", +} + def get_version() -> Text: return "" @@ -82,20 +97,12 @@ class HeaderInjection(VulnerabilityBase): def _iast_report_header_injection(headers_args) -> None: - headers_exclusion = { - "content-type", - "content-length", - "content-encoding", - "transfer-encoding", - "set-cookie", - "vary", - } from .._metrics import _set_metric_iast_executed_sink from .._taint_tracking import is_pyobject_tainted from .._taint_tracking.aspects import add_aspect header_name, header_value = headers_args - for header_to_exclude in headers_exclusion: + for header_to_exclude in HEADER_INJECTION_EXCLUSIONS: header_name_lower = header_name.lower() if header_name_lower == header_to_exclude or header_name_lower.startswith(header_to_exclude): return diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index fdb0bbb1af7..020cbe27a98 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -1067,20 +1067,70 @@ def header_injection(): loaded = json.loads(root_span.get_tag(IAST.JSON)) assert loaded["sources"] == [{"origin": "http.request.parameter", "name": "name", "value": "test"}] - line, hash_value = get_line_and_hash( - "test_flask_header_injection_label", - VULN_HEADER_INJECTION, - filename=TEST_FILE_PATH, - ) vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_HEADER_INJECTION assert vulnerability["evidence"] == { "valueParts": [{"value": "Header-Injection: "}, {"source": 0, "value": "test"}] } # TODO: vulnerability path is flaky, it points to "tests/contrib/flask/__init__.py" - # assert vulnerability["location"]["path"] == TEST_FILE_PATH - # assert vulnerability["location"]["line"] == line - # assert vulnerability["hash"] == hash_value + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_header_injection_exlusions_location(self): + @self.app.route("/header_injection/", methods=["GET", "POST"]) + def header_injection(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.headers["Location"] = tainted_string + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/header_injection/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + assert root_span.get_tag(IAST.JSON) is None + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_header_injection_exlusions_access_control(self): + @self.app.route("/header_injection/", methods=["GET", "POST"]) + def header_injection(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.headers["Access-Control-Allow-Example1"] = tainted_string + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/header_injection/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + assert root_span.get_tag(IAST.JSON) is None @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_flask_insecure_cookie(self): From 9ba5f9a43006489bc3258955821c8d6b30c74a06 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:34:20 -0400 Subject: [PATCH 047/372] chore(ci): update `beautifulsoup` framework test to use local `ddtrace` not PyPI latest (#11132) The `beautifulsoup` framework tests were using `pip install ddtrace` to install `ddtrace`, which was using the `latest` version of `ddtrace` from PyPI instead of the local version from the branch ([see example here](https://github.com/DataDog/dd-trace-py/actions/runs/11462716535/job/31894873509#step:5:19)). This PR fixes this to run `pip3 install ./ddtrace` instead so we're testing with the current version of `ddtrace` for the branch that triggered the test. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/test_frameworks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 6ea2513a763..8bc6be1cb16 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -974,7 +974,7 @@ jobs: git clone -b 4.12.3 https://git.launchpad.net/beautifulsoup - name: Install ddtrace if: needs.needs-run.outputs.outcome == 'success' - run: pip install ddtrace + run: pip3 install ./ddtrace - name: Pytest fix if: needs.needs-run.outputs.outcome == 'success' run: pip install pytest==8.2.1 From 9856f8dda5323bfd76f6712570cb9fd423998cc6 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Wed, 23 Oct 2024 12:14:59 -0400 Subject: [PATCH 048/372] chore: unbreak moved private function imports (#11140) https://github.com/DataDog/dd-trace-py/pull/11008 rearranged a bunch of code, but some of that code was already being used outside of this repo. Let's unbreak those imports for now. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/utils_botocore/span_pointers/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py index 8f6aab93445..c9765b30cfb 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py @@ -7,6 +7,12 @@ from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _DynamoDBItemFieldName from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _DynamoDBTableName from ddtrace._trace.utils_botocore.span_pointers.dynamodb import _extract_span_pointers_for_dynamodb_response + +# We are importing this function here because it used to live in this module +# and was imported from here in datadog-lambda-python. Once the import is fixed +# in the next release of that library, we should be able to remove this unused +# import from here as well. +from ddtrace._trace.utils_botocore.span_pointers.s3 import _aws_s3_object_span_pointer_description # noqa: F401 from ddtrace._trace.utils_botocore.span_pointers.s3 import _extract_span_pointers_for_s3_response From 2a4d8edf2ed53f203b2edd82ae047be892df5dbc Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 23 Oct 2024 13:10:34 -0400 Subject: [PATCH 049/372] chore(profiling): add gunicorn test for stack v2 (#11134) Copied and edited from https://github.com/DataDog/dd-trace-py/blob/a04689875b71cb99acb38a2a4c07cbaeb3bc6270/tests/profiling/test_gunicorn.py ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling_v2/test_gunicorn.py | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/profiling_v2/test_gunicorn.py diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py new file mode 100644 index 00000000000..a56dc1b8b07 --- /dev/null +++ b/tests/profiling_v2/test_gunicorn.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- +import os +import re +import subprocess +import sys +import time + +import pytest + +from tests.profiling.collector import pprof_utils + + +# gunicorn is not available on Windows +if sys.platform == "win32": + pytestmark = pytest.mark.skip + +TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) + + +def _run_gunicorn(*args): + cmd = ( + ["ddtrace-run", "gunicorn", "--bind", "127.0.0.1:7643", "--chdir", os.path.dirname(__file__)] + + list(args) + + ["tests.profiling.gunicorn-app:app"] + ) + return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + +@pytest.fixture +def gunicorn(monkeypatch): + # Do not ignore profiler so we have samples in the output pprof + monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "0") + monkeypatch.setenv("DD_PROFILING_ENABLED", "1") + # This was needed for the gunicorn process to start and print worker startup + # messages. Without this, the test can't find the worker PIDs. + monkeypatch.setenv("DD_PROFILING_STACK_V2_ENABLED", "1") + + yield _run_gunicorn + + +def _get_worker_pids(stdout): + # type: (str) -> list[int] + return [int(_) for _ in re.findall(r"Booting worker with pid: (\d+)", stdout)] + + +def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): + # type: (...) -> None + filename = str(tmp_path / "gunicorn.pprof") + monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) + + proc = gunicorn("-w", "3", *args) + time.sleep(3) + proc.terminate() + output = proc.stdout.read().decode() + worker_pids = _get_worker_pids(output) + + assert len(worker_pids) == 3, output + assert proc.wait() == 0, output + assert "module 'threading' has no attribute '_active'" not in output, output + + profile = pprof_utils.parse_profile("%s.%d" % (filename, proc.pid)) + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + assert len(samples) > 0 + + +def test_gunicorn(gunicorn, tmp_path, monkeypatch): + # type: (...) -> None + args = ("-k", "gevent") if TESTING_GEVENT else tuple() + _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args) From 2b410f95802b75528b3fb25023a942a5495b7f4a Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Wed, 23 Oct 2024 16:02:43 -0400 Subject: [PATCH 050/372] feat: support baggage (#10389) First PR introducing baggage support ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Co-authored-by: Zachary Groves <32471391+ZStriker19@users.noreply.github.com> Co-authored-by: Munir Abdinur --- ddtrace/_trace/context.py | 17 +- ddtrace/_trace/span.py | 11 - ddtrace/internal/constants.py | 8 +- ddtrace/llmobs/_llmobs.py | 10 +- ddtrace/propagation/http.py | 111 +++++++++- ddtrace/settings/config.py | 3 +- .../baggage-support-be7eed26293f1216.yaml | 3 + tests/tracer/test_propagation.py | 193 +++++++++++++++++- tests/tracer/test_span.py | 44 +++- 9 files changed, 357 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/baggage-support-be7eed26293f1216.yaml diff --git a/ddtrace/_trace/context.py b/ddtrace/_trace/context.py index 4bc36527338..07bc3960b56 100644 --- a/ddtrace/_trace/context.py +++ b/ddtrace/_trace/context.py @@ -218,7 +218,7 @@ def _trace_id_64bits(self): else: return _MAX_UINT_64BITS & self.trace_id - def _set_baggage_item(self, key: str, value: Any) -> None: + def set_baggage_item(self, key: str, value: Any) -> None: """Sets a baggage item in this span context. Note that this operation mutates the baggage of this span context """ @@ -237,10 +237,23 @@ def _with_baggage_item(self, key: str, value: Any) -> "Context": ctx._baggage = new_baggage return ctx - def _get_baggage_item(self, key: str) -> Optional[Any]: + def get_baggage_item(self, key: str) -> Optional[Any]: """Gets a baggage item in this span context.""" return self._baggage.get(key, None) + def get_all_baggage_items(self) -> Dict[str, Any]: + """Returns all baggage items in this span context.""" + return self._baggage + + def remove_baggage_item(self, key: str) -> None: + """Remove a baggage item from this span context.""" + if key in self._baggage: + del self._baggage[key] + + def remove_all_baggage_items(self) -> None: + """Removes all baggage items from this span context.""" + self._baggage.clear() + def __eq__(self, other: Any) -> bool: if isinstance(other, Context): with self._lock: diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index ae642b97a19..db90a769cd9 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -497,23 +497,12 @@ def get_metric(self, key: _TagNameType) -> Optional[NumericType]: """Return the given metric or None if it doesn't exist.""" return self._metrics.get(key) - def _set_baggage_item(self, key: str, value: Any) -> "Span": - """Sets a baggage item in the span context of this span. - Baggage is used to propagate state between spans (in-process, http/https). - """ - self._context = self.context._with_baggage_item(key, value) - return self - def _add_event( self, name: str, attributes: Optional[Dict[str, str]] = None, timestamp: Optional[int] = None ) -> None: """Add an event to the span.""" self._events.append(SpanEvent(name, attributes, timestamp)) - def _get_baggage_item(self, key: str) -> Optional[Any]: - """Gets a baggage item from the span context of this span.""" - return self.context._get_baggage_item(key) - def get_metrics(self) -> _MetricDictType: """Return all metrics.""" return self._metrics.copy() diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index e20f73e852a..bb55e657204 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -9,13 +9,15 @@ PROPAGATION_STYLE_B3_SINGLE = "b3" _PROPAGATION_STYLE_W3C_TRACECONTEXT = "tracecontext" _PROPAGATION_STYLE_NONE = "none" -_PROPAGATION_STYLE_DEFAULT = "datadog,tracecontext" +_PROPAGATION_STYLE_DEFAULT = "datadog,tracecontext,baggage" +_PROPAGATION_STYLE_BAGGAGE = "baggage" PROPAGATION_STYLE_ALL = ( _PROPAGATION_STYLE_W3C_TRACECONTEXT, PROPAGATION_STYLE_DATADOG, PROPAGATION_STYLE_B3_MULTI, PROPAGATION_STYLE_B3_SINGLE, _PROPAGATION_STYLE_NONE, + _PROPAGATION_STYLE_BAGGAGE, ) W3C_TRACESTATE_KEY = "tracestate" W3C_TRACEPARENT_KEY = "traceparent" @@ -67,6 +69,10 @@ _HTTPLIB_NO_TRACE_REQUEST = "_dd_no_trace" DEFAULT_TIMEOUT = 2.0 +# baggage +DD_TRACE_BAGGAGE_MAX_ITEMS = 64 +DD_TRACE_BAGGAGE_MAX_BYTES = 8192 + class _PRIORITY_CATEGORY: USER = "user" diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 42e27d33fb4..14ae70c1214 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -112,7 +112,7 @@ def _do_annotations(self, span): if span.span_type != SpanTypes.LLM: # do this check to avoid the warning log in `annotate` return current_context = self._instance.tracer.current_trace_context() - current_context_id = current_context._get_baggage_item(ANNOTATIONS_CONTEXT_ID) + current_context_id = current_context.get_baggage_item(ANNOTATIONS_CONTEXT_ID) with self._annotation_context_lock: for _, context_id, annotation_kwargs in self._instance._annotations: if current_context_id == context_id: @@ -301,12 +301,12 @@ def get_annotations_context_id(): ctx_id = annotation_id if current_ctx is None: current_ctx = Context(is_remote=False) - current_ctx._set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) + current_ctx.set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) cls._instance.tracer.context_provider.activate(current_ctx) - elif not current_ctx._get_baggage_item(ANNOTATIONS_CONTEXT_ID): - current_ctx._set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) + elif not current_ctx.get_baggage_item(ANNOTATIONS_CONTEXT_ID): + current_ctx.set_baggage_item(ANNOTATIONS_CONTEXT_ID, ctx_id) else: - ctx_id = current_ctx._get_baggage_item(ANNOTATIONS_CONTEXT_ID) + ctx_id = current_ctx.get_baggage_item(ANNOTATIONS_CONTEXT_ID) return ctx_id def register_annotation(): diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index abbb8e46c1a..343f653f548 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -8,6 +8,7 @@ from typing import Text # noqa:F401 from typing import Tuple # noqa:F401 from typing import cast # noqa:F401 +import urllib.parse import ddtrace from ddtrace._trace.span import Span # noqa:F401 @@ -38,8 +39,11 @@ from ..internal._tagset import decode_tagset_string from ..internal._tagset import encode_tagset_values from ..internal.compat import ensure_text +from ..internal.constants import _PROPAGATION_STYLE_BAGGAGE from ..internal.constants import _PROPAGATION_STYLE_NONE from ..internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT +from ..internal.constants import DD_TRACE_BAGGAGE_MAX_BYTES +from ..internal.constants import DD_TRACE_BAGGAGE_MAX_ITEMS from ..internal.constants import HIGHER_ORDER_TRACE_ID_BITS as _HIGHER_ORDER_TRACE_ID_BITS from ..internal.constants import LAST_DD_PARENT_ID_KEY from ..internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS @@ -74,6 +78,7 @@ _HTTP_HEADER_TAGS: Literal["x-datadog-tags"] = "x-datadog-tags" _HTTP_HEADER_TRACEPARENT: Literal["traceparent"] = "traceparent" _HTTP_HEADER_TRACESTATE: Literal["tracestate"] = "tracestate" +_HTTP_HEADER_BAGGAGE: Literal["baggage"] = "baggage" def _possible_header(header): @@ -127,7 +132,7 @@ def _attach_baggage_to_context(headers: Dict[str, str], context: Context): if context is not None: for key, value in headers.items(): if key[: len(_HTTP_BAGGAGE_PREFIX)] == _HTTP_BAGGAGE_PREFIX: - context._set_baggage_item(key[len(_HTTP_BAGGAGE_PREFIX) :], value) + context.set_baggage_item(key[len(_HTTP_BAGGAGE_PREFIX) :], value) def _hex_id_to_dd_id(hex_id): @@ -885,12 +890,77 @@ def _inject(span_context, headers): return headers +class _BaggageHeader: + """Helper class to inject/extract Baggage Headers""" + + SAFE_CHARACTERS_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "!#$%&'*+-.^_`|~" + SAFE_CHARACTERS_VALUE = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "!#$%&'()*+-./:<>?@[]^_`{|}~" + ) + + @staticmethod + def _encode_key(key: str) -> str: + return urllib.parse.quote(str(key).strip(), safe=_BaggageHeader.SAFE_CHARACTERS_KEY) + + @staticmethod + def _encode_value(value: str) -> str: + return urllib.parse.quote(str(value).strip(), safe=_BaggageHeader.SAFE_CHARACTERS_VALUE) + + @staticmethod + def _inject(span_context: Context, headers: Dict[str, str]) -> None: + baggage_items = span_context._baggage.items() + if not baggage_items: + return + + if len(baggage_items) > DD_TRACE_BAGGAGE_MAX_ITEMS: + log.warning("Baggage item limit exceeded") + return + + try: + header_value = ",".join( + f"{_BaggageHeader._encode_key(key)}={_BaggageHeader._encode_value(value)}" + for key, value in baggage_items + ) + + buf = bytes(header_value, "utf-8") + if len(buf) > DD_TRACE_BAGGAGE_MAX_BYTES: + log.warning("Baggage header size exceeded") + return + + headers[_HTTP_HEADER_BAGGAGE] = header_value + + except Exception: + log.warning("Failed to encode and inject baggage header") + + @staticmethod + def _extract(headers: Dict[str, str]) -> Context: + header_value = headers.get(_HTTP_HEADER_BAGGAGE) + + if not header_value: + return Context(baggage={}) + + baggage = {} + baggages = header_value.split(",") + for key_value in baggages: + if "=" not in key_value: + return Context(baggage={}) + key, value = key_value.split("=", 1) + key = urllib.parse.unquote(key.strip()) + value = urllib.parse.unquote(value.strip()) + if not key or not value: + return Context(baggage={}) + baggage[key] = value + + return Context(baggage=baggage) + + _PROP_STYLES = { PROPAGATION_STYLE_DATADOG: _DatadogMultiHeader, PROPAGATION_STYLE_B3_MULTI: _B3MultiHeader, PROPAGATION_STYLE_B3_SINGLE: _B3SingleHeader, _PROPAGATION_STYLE_W3C_TRACECONTEXT: _TraceContext, _PROPAGATION_STYLE_NONE: _NOP_Propagator, + _PROPAGATION_STYLE_BAGGAGE: _BaggageHeader, } @@ -906,6 +976,9 @@ def _extract_configured_contexts_avail(normalized_headers): for prop_style in config._propagation_style_extract: propagator = _PROP_STYLES[prop_style] context = propagator._extract(normalized_headers) + # baggage is handled separately + if prop_style == _PROPAGATION_STYLE_BAGGAGE: + continue if context: contexts.append(context) styles_w_ctx.append(prop_style) @@ -915,6 +988,7 @@ def _extract_configured_contexts_avail(normalized_headers): def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): primary_context = contexts[0] links = [] + for context in contexts[1:]: style_w_ctx = styles_w_ctx[contexts.index(context)] # encoding expects at least trace_id and span_id @@ -924,9 +998,11 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): context.trace_id, context.span_id, flags=1 if context.sampling_priority and context.sampling_priority > 0 else 0, - tracestate=context._meta.get(W3C_TRACESTATE_KEY, "") - if style_w_ctx == _PROPAGATION_STYLE_W3C_TRACECONTEXT - else None, + tracestate=( + context._meta.get(W3C_TRACESTATE_KEY, "") + if style_w_ctx == _PROPAGATION_STYLE_W3C_TRACECONTEXT + else None + ), attributes={ "reason": "terminated_context", "context_headers": style_w_ctx, @@ -952,6 +1028,7 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): primary_context._meta[LAST_DD_PARENT_ID_KEY] = "{:016x}".format(dd_context.span_id) # the span_id in tracecontext takes precedence over the first extracted propagation style primary_context.span_id = context.span_id + primary_context._span_links = links return primary_context @@ -999,6 +1076,10 @@ def parent_call(): else: log.error("ddtrace.tracer.sample is not available, unable to sample span.") + # baggage should be injected regardless of existing span or trace id + if _PROPAGATION_STYLE_BAGGAGE in config._propagation_style_inject: + _BaggageHeader._inject(span_context, headers) + # Not a valid context to propagate if span_context.trace_id is None or span_context.span_id is None: log.debug("tried to inject invalid context %r", span_context) @@ -1024,7 +1105,6 @@ def parent_call(): @staticmethod def extract(headers): - # type: (Dict[str,str]) -> Context """Extract a Context from HTTP headers into a new Context. For tracecontext propagation we extract tracestate headers for propagation even if another propagation style is specified before tracecontext, @@ -1050,16 +1130,17 @@ def my_controller(url, headers): return Context() try: normalized_headers = {name.lower(): v for name, v in headers.items()} - + context = Context() # tracer configured to extract first only if config._propagation_extract_first: # loop through the extract propagation styles specified in order, return whatever context we get first for prop_style in config._propagation_style_extract: propagator = _PROP_STYLES[prop_style] - context = propagator._extract(normalized_headers) # type: ignore - if config._propagation_http_baggage_enabled is True: + context = propagator._extract(normalized_headers) + if config.propagation_http_baggage_enabled is True: _attach_baggage_to_context(normalized_headers, context) - return context + break + # loop through all extract propagation styles else: contexts, styles_w_ctx = HTTPPropagator._extract_configured_contexts_avail(normalized_headers) @@ -1068,7 +1149,17 @@ def my_controller(url, headers): context = HTTPPropagator._resolve_contexts(contexts, styles_w_ctx, normalized_headers) if config._propagation_http_baggage_enabled is True: _attach_baggage_to_context(normalized_headers, context) - return context + + # baggage headers are handled separately from the other propagation styles + if _PROPAGATION_STYLE_BAGGAGE in config._propagation_style_extract: + baggage_context = _BaggageHeader._extract(normalized_headers) + if baggage_context._baggage != {}: + if context: + context._baggage = baggage_context._baggage + else: + context = baggage_context + + return context except Exception: log.debug("error while extracting context propagation headers", exc_info=True) diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 178145601a1..33105142b0b 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -112,10 +112,11 @@ def _parse_propagation_styles(styles_str): - "b3" (formerly 'b3 single header') - "b3 single header (deprecated for 'b3')" - "tracecontext" + - "baggage" - "none" - The default value is ``"datadog,tracecontext"``. + The default value is ``"datadog,tracecontext,baggage"``. Examples:: diff --git a/releasenotes/notes/baggage-support-be7eed26293f1216.yaml b/releasenotes/notes/baggage-support-be7eed26293f1216.yaml new file mode 100644 index 00000000000..95f44846b13 --- /dev/null +++ b/releasenotes/notes/baggage-support-be7eed26293f1216.yaml @@ -0,0 +1,3 @@ +features: + - | + tracing: Introduces support for Baggage as defined by the `OpenTelemetry specification `_. \ No newline at end of file diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 62f93ff2d04..d47fef3652d 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -15,6 +15,7 @@ from ddtrace.constants import AUTO_REJECT from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT +from ddtrace.internal.constants import _PROPAGATION_STYLE_BAGGAGE from ddtrace.internal.constants import _PROPAGATION_STYLE_NONE from ddtrace.internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT from ddtrace.internal.constants import LAST_DD_PARENT_ID_KEY @@ -28,6 +29,7 @@ from ddtrace.propagation.http import _HTTP_HEADER_B3_SINGLE from ddtrace.propagation.http import _HTTP_HEADER_B3_SPAN_ID from ddtrace.propagation.http import _HTTP_HEADER_B3_TRACE_ID +from ddtrace.propagation.http import _HTTP_HEADER_BAGGAGE from ddtrace.propagation.http import _HTTP_HEADER_TAGS from ddtrace.propagation.http import _HTTP_HEADER_TRACEPARENT from ddtrace.propagation.http import _HTTP_HEADER_TRACESTATE @@ -68,7 +70,7 @@ def test_inject(tracer): # noqa: F811 def test_inject_with_baggage_http_propagation(tracer): # noqa: F811 with override_global_config(dict(_propagation_http_baggage_enabled=True)): ctx = Context(trace_id=1234, sampling_priority=2, dd_origin="synthetics") - ctx._set_baggage_item("key1", "val1") + ctx.set_baggage_item("key1", "val1") tracer.context_provider.activate(ctx) with tracer.trace("global_root_span") as span: headers = {} @@ -282,6 +284,7 @@ def test_extract(tracer): # noqa: F811 "x-datadog-origin": "synthetics", "x-datadog-tags": "_dd.p.test=value,any=tag", "ot-baggage-key1": "value1", + "baggage": "foo=bar,racoon=cute,serverNode=DF%2028", } context = HTTPPropagator.extract(headers) @@ -308,6 +311,10 @@ def test_extract(tracer): # noqa: F811 "_dd.p.dm": "-3", "_dd.p.test": "value", } + assert context.get_baggage_item("foo") == "bar" + assert context.get_baggage_item("racoon") == "cute" + assert context.get_baggage_item("serverNode") == "DF 28" + assert len(context.get_all_baggage_items()) == 3 def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation(tracer): # noqa: F811 @@ -649,9 +656,9 @@ def test_extract_with_baggage_http_propagation(tracer): # noqa: F811 tracer.context_provider.activate(context) with tracer.trace("local_root_span") as span: - assert span._get_baggage_item("key1") == "value1" + assert span.context.get_baggage_item("key1") == "value1" with tracer.trace("child_span") as child_span: - assert child_span._get_baggage_item("key1") == "value1" + assert child_span.context.get_baggage_item("key1") == "value1" @pytest.mark.subprocess( @@ -2839,8 +2846,15 @@ def test_span_links_set_on_root_span_not_child(fastapi_client, tracer, fastapi_t PROPAGATION_STYLE_B3_MULTI, PROPAGATION_STYLE_B3_SINGLE, _PROPAGATION_STYLE_W3C_TRACECONTEXT, + _PROPAGATION_STYLE_BAGGAGE, ], - VALID_DATADOG_CONTEXT, + { + "trace_id": 13088165645273925489, + "span_id": 8185124618007618416, + "sampling_priority": 1, + "dd_origin": "synthetics", + "baggage": {"foo": "bar"}, + }, { HTTP_HEADER_TRACE_ID: "13088165645273925489", HTTP_HEADER_PARENT_ID: "8185124618007618416", @@ -2852,6 +2866,7 @@ def test_span_links_set_on_root_span_not_child(fastapi_client, tracer, fastapi_t _HTTP_HEADER_B3_SINGLE: "b5a2814f70060771-7197677932a62370-1", _HTTP_HEADER_TRACEPARENT: "00-0000000000000000b5a2814f70060771-7197677932a62370-01", _HTTP_HEADER_TRACESTATE: "dd=s:1;o:synthetics", + _HTTP_HEADER_BAGGAGE: "foo=bar", }, ), ( @@ -2918,6 +2933,60 @@ def test_span_links_set_on_root_span_not_child(fastapi_client, tracer, fastapi_t _HTTP_HEADER_TRACESTATE: "", }, ), + ( + "only_baggage", + [ + _PROPAGATION_STYLE_BAGGAGE, + ], + { + "baggage": {"foo": "bar"}, + }, + { + _HTTP_HEADER_BAGGAGE: "foo=bar", + }, + ), + ( + "baggage_and_datadog", + [ + PROPAGATION_STYLE_DATADOG, + _PROPAGATION_STYLE_BAGGAGE, + ], + { + "trace_id": VALID_DATADOG_CONTEXT["trace_id"], + "span_id": VALID_DATADOG_CONTEXT["span_id"], + "baggage": {"foo": "bar"}, + }, + { + HTTP_HEADER_TRACE_ID: "13088165645273925489", + HTTP_HEADER_PARENT_ID: "8185124618007618416", + _HTTP_HEADER_BAGGAGE: "foo=bar", + }, + ), + ( + "baggage_order_first", + [ + _PROPAGATION_STYLE_BAGGAGE, + PROPAGATION_STYLE_DATADOG, + PROPAGATION_STYLE_B3_MULTI, + PROPAGATION_STYLE_B3_SINGLE, + _PROPAGATION_STYLE_W3C_TRACECONTEXT, + ], + { + "baggage": {"foo": "bar"}, + "trace_id": VALID_DATADOG_CONTEXT["trace_id"], + "span_id": VALID_DATADOG_CONTEXT["span_id"], + }, + { + _HTTP_HEADER_BAGGAGE: "foo=bar", + HTTP_HEADER_TRACE_ID: "13088165645273925489", + HTTP_HEADER_PARENT_ID: "8185124618007618416", + _HTTP_HEADER_B3_TRACE_ID: "b5a2814f70060771", + _HTTP_HEADER_B3_SPAN_ID: "7197677932a62370", + _HTTP_HEADER_B3_SINGLE: "b5a2814f70060771-7197677932a62370", + _HTTP_HEADER_TRACEPARENT: "00-0000000000000000b5a2814f70060771-7197677932a62370-00", + _HTTP_HEADER_TRACESTATE: "", + }, + ), ] @@ -3040,3 +3109,119 @@ def test_llmobs_parent_id_not_injected_by_default(): context = Context(trace_id=1, span_id=2) HTTPPropagator.inject(context, {}) mock_llmobs_inject.assert_not_called() + + +@pytest.mark.parametrize( + "span_context,expected_headers", + [ + (Context(baggage={"key1": "val1"}), {"baggage": "key1=val1"}), + (Context(baggage={"key1": "val1", "key2": "val2"}), {"baggage": "key1=val1,key2=val2"}), + (Context(baggage={"serverNode": "DF 28"}), {"baggage": "serverNode=DF%2028"}), + (Context(baggage={"userId": "Amélie"}), {"baggage": "userId=Am%C3%A9lie"}), + (Context(baggage={"user!d(me)": "false"}), {"baggage": "user!d%28me%29=false"}), + ( + Context(baggage={'",;\\()/:<=>?@[]{}': '",;\\'}), + {"baggage": "%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C"}, + ), + ], + ids=[ + "single_key_value", + "multiple_key_value_pairs", + "space_in_value", + "special_characters_in_value", + "special_characters_in_key", + "special_characters_in_key_and_value", + ], +) +def test_baggageheader_inject(span_context, expected_headers): + from ddtrace.propagation.http import _BaggageHeader + + headers = {} + _BaggageHeader._inject(span_context, headers) + assert headers == expected_headers + + +def test_baggageheader_maxitems_inject(): + from ddtrace.internal.constants import DD_TRACE_BAGGAGE_MAX_ITEMS + from ddtrace.propagation.http import _BaggageHeader + + headers = {} + baggage_items = {} + for i in range(DD_TRACE_BAGGAGE_MAX_ITEMS + 1): + baggage_items[f"key{i}"] = f"val{i}" + span_context = Context(baggage=baggage_items) + _BaggageHeader._inject(span_context, headers) + assert "baggage" not in headers + + +def test_baggageheader_maxbytes_inject(): + from ddtrace.internal.constants import DD_TRACE_BAGGAGE_MAX_BYTES + from ddtrace.propagation.http import _BaggageHeader + + headers = {} + baggage_items = {"foo": ("a" * DD_TRACE_BAGGAGE_MAX_BYTES)} + span_context = Context(baggage=baggage_items) + _BaggageHeader._inject(span_context, headers) + assert "baggage" not in headers + + +@pytest.mark.parametrize( + "headers,expected_baggage", + [ + ({"baggage": "key1=val1"}, {"key1": "val1"}), + ({"baggage": "key1=val1,key2=val2,foo=bar,x=y"}, {"key1": "val1", "key2": "val2", "foo": "bar", "x": "y"}), + ({"baggage": "user!d%28me%29=false"}, {"user!d(me)": "false"}), + ({"baggage": "userId=Am%C3%A9lie"}, {"userId": "Amélie"}), + ({"baggage": "serverNode=DF%2028"}, {"serverNode": "DF 28"}), + ( + {"baggage": "%22%2C%3B%5C%28%29%2F%3A%3C%3D%3E%3F%40%5B%5D%7B%7D=%22%2C%3B%5C"}, + {'",;\\()/:<=>?@[]{}': '",;\\'}, + ), + ], + ids=[ + "single_key_value", + "multiple_key_value_pairs", + "special_characters_in_key", + "special_characters_in_value", + "space_in_value", + "special_characters_in_key_and_value", + ], +) +def test_baggageheader_extract(headers, expected_baggage): + from ddtrace.propagation.http import _BaggageHeader + + context = _BaggageHeader._extract(headers) + assert context._baggage == expected_baggage + + +@pytest.mark.parametrize( + "headers,expected_baggage", + [ + ({"baggage": "no-equal-sign,foo=gets-dropped-because-previous-pair-is-malformed"}, {}), + ({"baggage": "foo=gets-dropped-because-subsequent-pair-is-malformed,="}, {}), + ({"baggage": "=no-key"}, {}), + ({"baggage": "no-value="}, {}), + ], + ids=[ + "no-equal-sign-prev", + "no-equal-sign-subsequent", + "no-key", + "no-value", + ], +) +def test_baggage_malformedheader_extract(headers, expected_baggage): + from ddtrace.propagation.http import _BaggageHeader + + context = _BaggageHeader._extract(headers) + assert context._baggage == expected_baggage + + +@pytest.mark.parametrize( + "headers", + [ + {"baggage": "key1=val1,key2=val2,foo=bar,x=y"}, + ], +) +def test_http_propagator_baggage_extract(headers): + context = HTTPPropagator.extract(headers) + assert context._baggage == {"key1": "val1", "key2": "val2", "foo": "bar", "x": "y"} diff --git a/tests/tracer/test_span.py b/tests/tracer/test_span.py index fd8f995f569..e746632037e 100644 --- a/tests/tracer/test_span.py +++ b/tests/tracer/test_span.py @@ -98,20 +98,46 @@ def test_set_tag_bool(self): def test_set_baggage_item(self): s = Span(name="test.span") - s._set_baggage_item("custom.key", "123") - assert s._get_baggage_item("custom.key") == "123" + s.context.set_baggage_item("custom.key", "123") + assert s.context.get_baggage_item("custom.key") == "123" - def test_baggage_propagation(self): + def test_baggage_get(self): span1 = Span(name="test.span1") - span1._set_baggage_item("item1", "123") + span1.context.set_baggage_item("item1", "123") span2 = Span(name="test.span2", context=span1.context) - span2._set_baggage_item("item2", "456") + span2.context.set_baggage_item("item2", "456") - assert span2._get_baggage_item("item1") == "123" - assert span2._get_baggage_item("item2") == "456" - assert span1._get_baggage_item("item1") == "123" - assert span1._get_baggage_item("item2") is None + assert span2.context.get_baggage_item("item1") == "123" + assert span2.context.get_baggage_item("item2") == "456" + assert span1.context.get_baggage_item("item1") == "123" + + def test_baggage_remove(self): + span1 = Span(name="test.span1") + span1.context.set_baggage_item("item1", "123") + span1.context.set_baggage_item("item2", "456") + + span1.context.remove_baggage_item("item1") + assert span1.context.get_baggage_item("item1") is None + assert span1.context.get_baggage_item("item2") == "456" + span1.context.remove_baggage_item("item2") + assert span1.context.get_baggage_item("item2") is None + + def test_baggage_remove_all(self): + span1 = Span(name="test.span1") + span1.context.set_baggage_item("item1", "123") + span1.context.set_baggage_item("item2", "456") + + span1.context.remove_all_baggage_items() + assert span1.context.get_baggage_item("item1") is None + assert span1.context.get_baggage_item("item2") is None + + def test_baggage_get_all(self): + span1 = Span(name="test.span1") + span1.context.set_baggage_item("item1", "123") + span1.context.set_baggage_item("item2", "456") + + assert span1.context.get_all_baggage_items() == {"item1": "123", "item2": "456"} def test_set_tag_metric(self): s = Span(name="test.span") From 0b87e352035376364dc7f48dbf046013417ac4f9 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 24 Oct 2024 10:16:03 +0200 Subject: [PATCH 051/372] chore(iast): restore Match tests (#11114) IAST context refactor found a problem with tainted re.Match objects https://github.com/DataDog/dd-trace-py/pull/10988/files#diff-85e91d30afa95f5cf701f8187200d0bdcec50d55999ef03708d49fa5a5ccb1d6R127 It looks this PR fixed the problem https://github.com/DataDog/dd-trace-py/pull/11042 So, this PR enables again Match ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/iast/leak_functions.py | 3 +- scripts/iast/mod_leak_functions.py | 6 +-- .../aspects/test_index_aspect_fixtures.py | 5 -- .../appsec/iast/fixtures/propagation_path.py | 47 +++++++++---------- .../iast_memcheck/test_iast_mem_check.py | 2 +- 5 files changed, 27 insertions(+), 36 deletions(-) diff --git a/scripts/iast/leak_functions.py b/scripts/iast/leak_functions.py index be416da211d..55fdcb0bbaa 100644 --- a/scripts/iast/leak_functions.py +++ b/scripts/iast/leak_functions.py @@ -70,8 +70,7 @@ async def iast_leaks(iterations: int, fail_percent: float, print_every: int): for i in range(iterations): _start_iast_context_and_oce() result = await test_doit() - # TODO(avara1986): `Match` contains errors. APPSEC-55239 - # assert result == "DDD_III_extend", f"result is {result}" + assert result == "DDD_III_extend", f"result is {result}" assert is_pyobject_tainted(result) _end_iast_context_and_oce() diff --git a/scripts/iast/mod_leak_functions.py b/scripts/iast/mod_leak_functions.py index a71a4a0414d..40e7e5a99b7 100644 --- a/scripts/iast/mod_leak_functions.py +++ b/scripts/iast/mod_leak_functions.py @@ -297,7 +297,7 @@ async def test_doit(): string8_5 = format_variants(string8_4, string1) await anyio.to_thread.run_sync(modulo_exceptions, string8_5) - string8_6 = string8_5[65:150] + string8_6 = string8_5[25:150] string9 = "notainted#{}".format(string8_6) string9_2 = f"{string9}_notainted" @@ -321,9 +321,7 @@ async def test_doit(): string18 = os.path.splitext(string17 + ".jpg")[0] string19 = os.path.normcase(string18) string20 = os.path.splitdrive(string19)[1] - # TODO(avara1986): Re.Match contains errors. APPSEC-55239 - # string21 = re_module(string20) - string21 = string20 + string21 = re_module(string20) tmp_str2 = "_extend" string21 += tmp_str2 diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 27282defb7b..0542cd636c8 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -9,7 +9,6 @@ from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module -from tests.utils import flaky from tests.utils import override_global_config @@ -121,7 +120,6 @@ def test_propagate_ranges_with_no_context(caplog): assert not any("[IAST] " in message for message in log_messages), log_messages -@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") def test_re_match_index_indexerror(): regexp = r"(?P\w+)@(?P\w+)\.(?P\w+)" @@ -139,7 +137,6 @@ def test_re_match_index_indexerror(): mod.do_re_match_index(string_input, regexp, "doesntexist") -@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.parametrize( "input_str, index, tainted, expected_result, ", [ @@ -176,7 +173,6 @@ def test_re_match_index(input_str, index, tainted, expected_result): assert len(get_tainted_ranges(result)) == int(tainted) -@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") def test_re_match_index_indexerror_bytes(): regexp = rb"(?P\w+)@(?P\w+)\.(?P\w+)" @@ -194,7 +190,6 @@ def test_re_match_index_indexerror_bytes(): mod.do_re_match_index(string_input, regexp, b"doesntexist") -@flaky(until=1706677200, reason="TODO(avara1986): Re.Match contains errors. APPSEC-55239") @pytest.mark.parametrize( "input_str, index, tainted, expected_result, ", [ diff --git a/tests/appsec/iast/fixtures/propagation_path.py b/tests/appsec/iast/fixtures/propagation_path.py index 8a8853d6564..b8ecfdbd990 100644 --- a/tests/appsec/iast/fixtures/propagation_path.py +++ b/tests/appsec/iast/fixtures/propagation_path.py @@ -4,6 +4,7 @@ """ import asyncio import os +import re import sys import _io @@ -178,30 +179,28 @@ def propagation_memory_check(origin_string1, tainted_string_2): else: string23 = string21 - # TODO(avara1986): Re.Match contains errors. APPSEC-55239 - # re_slash = re.compile(r"[_.][a-zA-Z]*") - # string24 = re_slash.findall(string23)[0] # 1 propagation: '_HIROOT - # - # re_match = re.compile(r"(\w+)", re.IGNORECASE) - # re_match_result = re_match.match(string24) # 1 propagation: 'HIROOT - # - # string25 = re_match_result.group(0) # 1 propagation: '_HIROOT - # - # tmp_str = "DDDD" - # string25 = tmp_str + string25 # 1 propagation: 'DDDD_HIROOT - # - # re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) - # re_match_result = re_match.search(string25) - # string26 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDDD_HIROOT - # - # re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) - # re_split_result = re_split.split(string26) - # - # # TODO(avara1986): DDDD_ is constant but we're tainting all re results - # string27 = re_split_result[0] + " EEE" - # string28 = re.sub(r" EEE", "_OOO", string27, re.IGNORECASE) - # string29 = re.subn(r"OOO", "III", string28, re.IGNORECASE)[0] - string29 = string23 + re_slash = re.compile(r"[_.][a-zA-Z]*") + string24 = re_slash.findall(string23)[0] # 1 propagation: '_HIROOT + + re_match = re.compile(r"(\w+)", re.IGNORECASE) + re_match_result = re_match.match(string24) # 1 propagation: 'HIROOT + + string25 = re_match_result.group(0) # 1 propagation: '_HIROOT + + tmp_str = "DDDD" + string25 = tmp_str + string25 # 1 propagation: 'DDDD_HIROOT + + re_match = re.compile(r"(\w+)(_+)(\w+)", re.IGNORECASE) + re_match_result = re_match.search(string25) + string26 = re_match_result.expand(r"DDD_\3") # 1 propagation: 'DDDD_HIROOT + + re_split = re.compile(r"[_.][a-zA-Z]*", re.IGNORECASE) + re_split_result = re_split.split(string26) + + # TODO(avara1986): DDDD_ is constant but we're tainting all re results + string27 = re_split_result[0] + " EEE" + string28 = re.sub(r" EEE", "_OOO", string27, re.IGNORECASE) + string29 = re.subn(r"OOO", "III", string28, re.IGNORECASE)[0] tmp_str2 = "_extend" string29 += tmp_str2 try: diff --git a/tests/appsec/iast_memcheck/test_iast_mem_check.py b/tests/appsec/iast_memcheck/test_iast_mem_check.py index 6440c36bc7a..d427f124aae 100644 --- a/tests/appsec/iast_memcheck/test_iast_mem_check.py +++ b/tests/appsec/iast_memcheck/test_iast_mem_check.py @@ -83,7 +83,7 @@ def test_propagation_memory_check(origin1, origin2, iast_context_defaults): span_report = _get_span_report() assert len(span_report.sources) > 0 assert len(span_report.vulnerabilities) > 0 - assert len(get_tainted_ranges(result)) == 6 + assert len(get_tainted_ranges(result)) == 1 if _num_objects_tainted == 0: _num_objects_tainted = num_objects_tainted() From 793275ab79da01675f688ab3fbc0b4823fd482be Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 24 Oct 2024 11:13:13 +0200 Subject: [PATCH 052/372] chore(asm): move waf info report to asm_context and add return code in ddwaf_result (#11137) - move span tag logic for ddwaf info data from processor to asm context to do it only once per request. - move serialisation of errors from processor to waf interface to do it only when waf is initialised or updated. - remove warnings log for span tag logic and corresponding test. (json errors are impossible now due to ddwaf_object use) - add return code from ddwaf_run into DDWaf_result object Preparatory PR for APPSEC-55199 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_asm_request_context.py | 22 ++++++++++++++ ddtrace/appsec/_ddwaf/__init__.py | 41 +++++++++++++++++++------- ddtrace/appsec/_processor.py | 17 ++--------- tests/appsec/appsec/test_processor.py | 41 ++------------------------ 4 files changed, 58 insertions(+), 63 deletions(-) diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 7b266e5a4c2..6a543ac3f56 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: + from ddtrace.appsec._ddwaf import DDWaf_info from ddtrace.appsec._ddwaf import DDWaf_result log = get_logger(__name__) @@ -68,6 +69,7 @@ def __init__(self, span: Optional[Span] = None): log.debug("ASM context created without an available span") context_span = tracer.trace("asm.context") self.span: Span = context_span + self.waf_info: Optional[Callable[[], "DDWaf_info"]] = None self.waf_addresses: Dict[str, Any] = {} self.callbacks: Dict[str, Any] = {_CONTEXT_CALL: []} self.telemetry: Dict[str, Any] = { @@ -212,6 +214,18 @@ def finalize_asm_env(env: ASM_Environment) -> None: for function in callbacks: function(env) flush_waf_triggers(env) + if env.waf_info: + info = env.waf_info() + try: + if info.errors: + env.span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors) + log.debug("Error in ASM In-App WAF: %s", info.errors) + env.span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version) + env.span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded) + env.span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed) + except Exception: + log.debug("Error executing ASM In-App WAF metrics report: %s", exc_info=True) + core.discard_local_item(_ASM_CONTEXT) @@ -296,6 +310,14 @@ def set_waf_callback(value) -> None: set_value(_CALLBACKS, _WAF_CALL, value) +def set_waf_info(info: Callable[[], "DDWaf_info"]) -> None: + env = _get_asm_context() + if env is None: + log.debug("setting waf info with no active asm context") + return + env.waf_info = info + + def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> Optional["DDWaf_result"]: if not asm_config._asm_enabled: return None diff --git a/ddtrace/appsec/_ddwaf/__init__.py b/ddtrace/appsec/_ddwaf/__init__.py index 8b1f487e6a4..478cf598231 100644 --- a/ddtrace/appsec/_ddwaf/__init__.py +++ b/ddtrace/appsec/_ddwaf/__init__.py @@ -1,4 +1,5 @@ import ctypes +import json import time from typing import Any from typing import Dict @@ -35,12 +36,19 @@ else: _DDWAF_LOADED = False +DDWAF_ERR_INTERNAL = -3 +DDWAF_ERR_INVALID_OBJECT = -2 +DDWAF_ERR_INVALID_ARGUMENT = -1 +DDWAF_OK = 0 +DDWAF_MATCH = 1 + class DDWaf_result(object): - __slots__ = ["data", "actions", "runtime", "total_runtime", "timeout", "truncation", "derivatives"] + __slots__ = ["return_code", "data", "actions", "runtime", "total_runtime", "timeout", "truncation", "derivatives"] def __init__( self, + return_code: int, data: List[Dict[str, Any]], actions: Dict[str, Any], runtime: float, @@ -49,6 +57,7 @@ def __init__( truncation: int, derivatives: Dict[str, Any], ): + self.return_code = return_code self.data = data self.actions = actions self.runtime = runtime @@ -59,7 +68,8 @@ def __init__( def __repr__(self): return ( - f"DDWaf_result(data: {self.data}, actions: {self.actions}, runtime: {self.runtime}," + f"DDWaf_result(return_code: {self.return_code} data: {self.data}," + f" actions: {self.actions}, runtime: {self.runtime}," f" total_runtime: {self.total_runtime}, timeout: {self.timeout}," f" truncation: {self.truncation}, derivatives: {self.derivatives})" ) @@ -68,7 +78,7 @@ def __repr__(self): class DDWaf_info(object): __slots__ = ["loaded", "failed", "errors", "version"] - def __init__(self, loaded: int, failed: int, errors: Dict[str, Any], version: str): + def __init__(self, loaded: int, failed: int, errors: str, version: str): self.loaded = loaded self.failed = failed self.errors = errors @@ -78,7 +88,7 @@ def __repr__(self): return "{loaded: %d, failed: %d, errors: %s, version: %s}" % ( self.loaded, self.failed, - str(self.errors), + self.errors, self.version, ) @@ -117,12 +127,17 @@ def required_data(self) -> List[str]: return py_ddwaf_known_addresses(self._handle) if self._handle else [] def _set_info(self, diagnostics: ddwaf_object) -> None: - info_struct = diagnostics.struct - rules = info_struct.get("rules", {}) if info_struct else {} # type: ignore + info_struct: dict[str, Any] = diagnostics.struct or {} # type: ignore + rules = info_struct.get("rules", {}) errors_result = rules.get("errors", {}) - version = info_struct.get("ruleset_version", self._cached_version) if info_struct else self._cached_version # type: ignore + version = info_struct.get("ruleset_version", self._cached_version) self._cached_version = version - self._info = DDWaf_info(len(rules.get("loaded", [])), len(rules.get("failed", [])), errors_result, version) + self._info = DDWaf_info( + len(rules.get("loaded", [])), + len(rules.get("failed", [])), + json.dumps(errors_result, separators=(",", ":")) if errors_result else "", + version, + ) ddwaf_object_free(diagnostics) @property @@ -165,7 +180,7 @@ def run( start = time.time() if not ctx: LOGGER.debug("DDWaf.run: dry run. no context created.") - return DDWaf_result([], {}, 0, (time.time() - start) * 1e6, False, 0, {}) + return DDWaf_result(0, [], {}, 0, (time.time() - start) * 1e6, False, 0, {}) result = ddwaf_result() observator = _observator() @@ -174,7 +189,11 @@ def run( error = ddwaf_run(ctx.ctx, wrapper, wrapper_ephemeral, ctypes.byref(result), int(timeout_ms * 1000)) if error < 0: LOGGER.debug("run DDWAF error: %d\ninput %s\nerror %s", error, wrapper.struct, self.info.errors) + if error == DDWAF_ERR_INTERNAL: + # result is not valid + return DDWaf_result(error, [], {}, 0, 0, False, 0, {}) return DDWaf_result( + error, result.events.struct, result.actions.struct, result.total_runtime / 1e3, @@ -191,7 +210,7 @@ def version() -> str: # Mockup of the DDWaf class doing nothing class DDWaf(object): # type: ignore required_data: List[str] = [] - info: DDWaf_info = DDWaf_info(0, 0, {}, "") + info: DDWaf_info = DDWaf_info(0, 0, "", "") def __init__( self, @@ -209,7 +228,7 @@ def run( timeout_ms: float = DEFAULT.WAF_TIMEOUT, ) -> DDWaf_result: LOGGER.debug("DDWaf features disabled. dry run") - return DDWaf_result([], {}, 0.0, 0.0, False, 0, {}) + return DDWaf_result(0, [], {}, 0.0, 0.0, False, 0, {}) def update_rules(self, _: Dict[str, Any]) -> bool: LOGGER.debug("DDWaf features disabled. dry update") diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 0cdf1003e2a..239961cb39f 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -329,6 +329,9 @@ def _waf_action( waf_results = self._ddwaf.run( ctx, data, ephemeral_data=ephemeral_data or None, timeout_ms=asm_config._waf_timeout ) + + _asm_request_context.set_waf_info(lambda: self._ddwaf.info) + blocked = {} for action, parameters in waf_results.actions.items(): if action == WAF_ACTIONS.BLOCK_ACTION: @@ -364,20 +367,6 @@ def _waf_action( if blocked: _asm_request_context.set_blocked(blocked) - try: - info = self._ddwaf.info - if info.errors: - errors = json.dumps(info.errors) - span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, errors) - log.debug("Error in ASM In-App WAF: %s", errors) - span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version) - span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded) - span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed) - except (JSONDecodeError, ValueError): - log.warning("Error parsing data ASM In-App WAF metrics report %s", info.errors) - except Exception: - log.warning("Error executing ASM In-App WAF metrics report: %s", exc_info=True) - if waf_results.data or blocked: # We run the rate limiter only if there is an attack, its goal is to limit the number of collected asm # events diff --git a/tests/appsec/appsec/test_processor.py b/tests/appsec/appsec/test_processor.py index c9f6e2e2d3a..3fec599237b 100644 --- a/tests/appsec/appsec/test_processor.py +++ b/tests/appsec/appsec/test_processor.py @@ -503,7 +503,7 @@ def test_ddwaf_info(): info = _ddwaf.info assert info.loaded == len(rules_json["rules"]) assert info.failed == 0 - assert info.errors == {} + assert info.errors == "" assert info.version == "rules_good" @@ -519,7 +519,7 @@ def test_ddwaf_info_with_2_errors(): expected_dict = sorted( {"missing key 'conditions'": ["crs-913-110"], "missing key 'tags'": ["crs-942-100"]}.items() ) - assert sorted(info.errors.items()) == expected_dict + assert sorted(json.loads(info.errors).items()) == expected_dict assert info.version == "5.5.5" @@ -531,42 +531,7 @@ def test_ddwaf_info_with_3_errors(): info = _ddwaf.info assert info.loaded == 1 assert info.failed == 2 - assert info.errors == {"missing key 'name'": ["crs-942-100", "crs-913-120"]} - - -def test_ddwaf_info_with_json_decode_errors(tracer, caplog): - config = rules.Config() - config.http_tag_query_string = True - - with caplog.at_level(logging.WARNING), mock.patch( - "ddtrace.appsec._processor.json.dumps", side_effect=JSONDecodeError("error", "error", 0) - ), mock.patch.object(DDWaf, "info"): - with asm_context(tracer=tracer, config=config_asm) as span: - set_http_meta( - span, - config, - method="PATCH", - url="http://localhost/api/unstable/role_requests/dab1e9ae-9d99-11ed-bfdf-da7ad0900000?_authentication_token=2b0297348221f294de3a047e2ecf1235abb866b6", # noqa: E501 - status_code="200", - raw_uri="http://localhost/api/unstable/role_requests/dab1e9ae-9d99-11ed-bfdf-da7ad0900000?_authentication_token=2b0297348221f294de3a047e2ecf1235abb866b6", # noqa: E501 - request_headers={ - "host": "localhost", - "user-agent": "aa", - "content-length": "73", - }, - response_headers={ - "content-length": "501", - "x-ratelimit-remaining": "363", - "x-ratelimit-name": "role_api", - "x-ratelimit-limit": "500", - "x-ratelimit-period": "60", - "content-type": "application/json", - "x-ratelimit-reset": "16", - }, - request_body={"_authentication_token": "2b0297348221f294de3a047e2ecf1235abb866b6"}, - ) - - assert "Error parsing data ASM In-App WAF metrics report" in caplog.text + assert json.loads(info.errors) == {"missing key 'name'": ["crs-942-100", "crs-913-120"]} def test_ddwaf_run_contained_typeerror(tracer, caplog): From c2ebba1d656f94bc651e5cef60ff8cad46438cf1 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 24 Oct 2024 10:36:02 +0100 Subject: [PATCH 053/372] fix(di): use enum value for evaluate_at property (#11100) We make sure that we get the value from the enum for the evaluate_at property so that proper validation and comparison can be performed. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_probe/remoteconfig.py | 3 ++- .../fix-span-decoration-probe-timing-dc4ce0664fa645b9.yaml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-span-decoration-probe-timing-dc4ce0664fa645b9.yaml diff --git a/ddtrace/debugging/_probe/remoteconfig.py b/ddtrace/debugging/_probe/remoteconfig.py index 8c11b95b816..31c6a2a42e3 100644 --- a/ddtrace/debugging/_probe/remoteconfig.py +++ b/ddtrace/debugging/_probe/remoteconfig.py @@ -24,6 +24,7 @@ from ddtrace.debugging._probe.model import MetricFunctionProbe from ddtrace.debugging._probe.model import MetricLineProbe from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod from ddtrace.debugging._probe.model import ProbeType from ddtrace.debugging._probe.model import SpanDecoration from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe @@ -102,7 +103,7 @@ def build(cls, args: Dict[str, Any], attribs: Dict[str, Any]) -> Any: args["module"] = where.get("type") or where["typeName"] args["func_qname"] = where.get("method") or where["methodName"] - args["evaluate_at"] = attribs.get("evaluateAt") + args["evaluate_at"] = ProbeEvaluateTimingForMethod[attribs.get("evaluateAt", "DEFAULT")] return cls.__function_class__(**args) diff --git a/releasenotes/notes/fix-span-decoration-probe-timing-dc4ce0664fa645b9.yaml b/releasenotes/notes/fix-span-decoration-probe-timing-dc4ce0664fa645b9.yaml new file mode 100644 index 00000000000..0fc170e6e9a --- /dev/null +++ b/releasenotes/notes/fix-span-decoration-probe-timing-dc4ce0664fa645b9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + dynamic instrumentation: Fixes an issue that prevented dynamic span tags + probes from adding the requested tags to the requested span. From 0cee2f066fb6e79aa15947c1514c0f406dea47c5 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 24 Oct 2024 11:39:40 +0200 Subject: [PATCH 054/372] chore(iast): fix stringio and bytesio aspects range offsets (#11135) ## Description Currently, given that we cannot know the current read index on `_io` objects (`StringIO` and `BytesIO`) we decided that if the `_io` object was tainted, everything `read` from it would also be tainted. There was, however, and issue were the results of `read` would not have their offsets starting from 0 or the right length for the read content. This PR fixes that. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_common_module_patches.py | 19 ++- .../appsec/_iast/_taint_tracking/aspects.py | 4 +- tests/appsec/iast/aspects/test_io_aspects.py | 136 ++++++++++++++++++ tests/appsec/iast/aspects/test_stringio.py | 52 ------- 4 files changed, 152 insertions(+), 59 deletions(-) create mode 100644 tests/appsec/iast/aspects/test_io_aspects.py delete mode 100644 tests/appsec/iast/aspects/test_stringio.py diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index c41350fd2ad..494e8e63fb7 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -52,11 +52,20 @@ def wrapped_read_F3E51D71B4EC16EF(original_read_callable, instance, args, kwargs """ result = original_read_callable(*args, **kwargs) if asm_config._iast_enabled: - from ddtrace.appsec._iast._taint_tracking import copy_and_shift_ranges_from_strings - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted - - if is_pyobject_tainted(instance): - copy_and_shift_ranges_from_strings(instance, result, 0) + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import Source + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + ranges = get_tainted_ranges(instance) + if len(ranges) > 0: + source = ranges[0].source if ranges[0].source else Source(name="_io", value=result, origin=OriginType.EMPTY) + result = taint_pyobject( + pyobject=result, + source_name=source.name, + source_value=source.value, + source_origin=source.origin, + ) return result diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 732f90ceac0..c009e1be1fb 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -116,7 +116,7 @@ def stringio_aspect(orig_function: Optional[Callable], flag_added_args: int, *ar if args and is_pyobject_tainted(args[0]) and isinstance(result, _io.StringIO): try: - copy_and_shift_ranges_from_strings(args[0], result, 0) + copy_ranges_from_strings(args[0], result) except Exception as e: iast_taint_log_error("IAST propagation error. stringio_aspect. {}".format(e)) return result @@ -134,7 +134,7 @@ def bytesio_aspect(orig_function: Optional[Callable], flag_added_args: int, *arg if args and is_pyobject_tainted(args[0]) and isinstance(result, _io.BytesIO): try: - copy_and_shift_ranges_from_strings(args[0], result, 0) + copy_ranges_from_strings(args[0], result) except Exception as e: iast_taint_log_error("IAST propagation error. bytesio_aspect. {}".format(e)) return result diff --git a/tests/appsec/iast/aspects/test_io_aspects.py b/tests/appsec/iast/aspects/test_io_aspects.py new file mode 100644 index 00000000000..ea74825895d --- /dev/null +++ b/tests/appsec/iast/aspects/test_io_aspects.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +import pytest + +from ddtrace.appsec._common_module_patches import patch_common_modules +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import bytesio_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import stringio_aspect +from tests.utils import override_global_config + + +@pytest.mark.parametrize( + "aspect, text", + [ + (stringio_aspect, "foobar"), + (bytesio_aspect, b"foobar"), + ], +) +def test_stringio_aspect_read(aspect, text): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + tainted = taint_pyobject( + pyobject=text, + source_name="test_stringio_read_aspect_tainted_string", + source_value=text, + source_origin=OriginType.PARAMETER, + ) + sio = aspect(None, 0, tainted) + val = sio.read() + assert is_pyobject_tainted(val) + ranges = get_tainted_ranges(val) + assert len(ranges) == 1 + assert ranges[0].start == 0 + assert ranges[0].length == 6 + + +@pytest.mark.skip("TODO: APPSEC-55319") +@pytest.mark.parametrize( + "aspect, text, added_text", + [ + (stringio_aspect, "foobar", "foobazbazfoo"), + (bytesio_aspect, b"foobar", b"foobazbazfoo"), + ], +) +def test_stringio_aspect_read_with_offset(aspect, text, added_text): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + not_tainted = added_text + tainted = taint_pyobject( + pyobject=text, + source_name="test_stringio_read_aspect_tainted_string", + source_value=text, + source_origin=OriginType.PARAMETER, + ) + added = add_aspect(not_tainted, tainted) + sio = aspect(None, 0, added) + val = sio.read(10) + # If the StringIO() and read() aspects were perfect, `val` would not be tainted + assert not is_pyobject_tainted(val) + ranges = get_tainted_ranges(val) + assert len(ranges) == 0 + + val_tainted = sio.read(5) + assert is_pyobject_tainted(val_tainted) + ranges = get_tainted_ranges(val_tainted) + assert len(ranges) == 1 + + +# Check the current behaviour of always tainting read() results from offset 0 +@pytest.mark.parametrize( + "aspect, text, added_text", + [ + (stringio_aspect, "foobar", "foobazbazfoo"), + (bytesio_aspect, b"foobar", b"foobazbazfoo"), + ], +) +def test_stringio_always_tainted_from_zero(aspect, text, added_text): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + not_tainted = added_text + tainted = taint_pyobject( + pyobject=text, + source_name="test_stringio_read_aspect_tainted_string", + source_value=text, + source_origin=OriginType.PARAMETER, + ) + added = add_aspect(not_tainted, tainted) + sio = aspect(None, 0, added) + read_len = 10 + val = sio.read(read_len) + # If the StringIO() and read() aspects were perfect, `val` would not be tainted + ranges = get_tainted_ranges(val) + assert len(ranges) == 1 + assert ranges[0].start == 0 + assert ranges[0].length == read_len + + read_len = 5 + val_tainted = sio.read(read_len) + ranges = get_tainted_ranges(val_tainted) + assert len(ranges) == 1 + assert ranges[0].start == 0 + assert ranges[0].length == read_len + assert is_pyobject_tainted(val_tainted) + + +# Check the current behaviour of always tainting read() results from offset 0 +def test_bytesio_always_tainted_from_zero(): + with override_global_config(dict(_iast_enabled=True)): + patch_common_modules() + not_tainted = b"foobazbazfoo" + tainted = taint_pyobject( + pyobject=b"foobar", + source_name="test_bytesio_read_aspect_tainted_string", + source_value=b"foobar", + source_origin=OriginType.PARAMETER, + ) + added = add_aspect(not_tainted, tainted) + sio = bytesio_aspect(None, 0, added) + read_len = 10 + val = sio.read(read_len) + # If the bytesio() and read() aspects were perfect, `val` would not be tainted + ranges = get_tainted_ranges(val) + assert len(ranges) == 1 + assert ranges[0].start == 0 + assert ranges[0].length == read_len + + read_len = 5 + val_tainted = sio.read(read_len) + ranges = get_tainted_ranges(val_tainted) + assert len(ranges) == 1 + assert ranges[0].start == 0 + assert ranges[0].length == read_len + assert is_pyobject_tainted(val_tainted) diff --git a/tests/appsec/iast/aspects/test_stringio.py b/tests/appsec/iast/aspects/test_stringio.py deleted file mode 100644 index cab99ebe24b..00000000000 --- a/tests/appsec/iast/aspects/test_stringio.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -import pytest - -from ddtrace.appsec._common_module_patches import patch_common_modules -from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject -from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect -from ddtrace.appsec._iast._taint_tracking.aspects import stringio_aspect -from tests.utils import override_global_config - - -def test_stringio_aspect_read(): - with override_global_config(dict(_iast_enabled=True)): - patch_common_modules() - tainted = taint_pyobject( - pyobject="foobar", - source_name="test_stringio_read_aspect_tainted_string", - source_value="foobar", - source_origin=OriginType.PARAMETER, - ) - sio = stringio_aspect(None, 0, tainted) - val = sio.read() - assert is_pyobject_tainted(val) - ranges = get_tainted_ranges(val) - assert len(ranges) == 1 - - -@pytest.mark.skip("TODO: APPSEC-55319") -def test_stringio_aspect_read_with_offset(): - with override_global_config(dict(_iast_enabled=True)): - patch_common_modules() - not_tainted = "foobazbazfoo" - tainted = taint_pyobject( - pyobject="foobar", - source_name="test_stringio_read_aspect_tainted_string", - source_value="foobar", - source_origin=OriginType.PARAMETER, - ) - added = add_aspect(not_tainted, tainted) - sio = stringio_aspect(None, 0, added) - val = sio.read(10) - # If the StringIO() and read() aspects were perfect, `val` would not be tainted - assert not is_pyobject_tainted(val) - ranges = get_tainted_ranges(val) - assert len(ranges) == 0 - - val_tainted = sio.read(5) - assert is_pyobject_tainted(val_tainted) - ranges = get_tainted_ranges(val_tainted) - assert len(ranges) == 1 From 26ad98b2f1272917996c60ebaa81ddfd033bae49 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 24 Oct 2024 11:35:13 +0100 Subject: [PATCH 055/372] perf(di): no path resolution in code origin for spans (#11118) We avoid resolving paths in code origin for spans as this is an expensive operation. Because there is path resolution logic in the backend, this step is not strictly required. Should this change in the future we can look into adding a cache instead. ![image](https://github.com/user-attachments/assets/9d2eccb9-64a5-4941-b084-3728969905a5) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_origin/span.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index a373608b43a..173cb40b128 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -207,14 +207,14 @@ def on_span_start(self, span: Span) -> None: seq = count(0) for frame in frame_stack(sys._getframe(1)): code = frame.f_code - frame_origin = Path(code.co_filename) + filename = code.co_filename - if is_user_code(frame_origin): + if is_user_code(Path(filename)): n = next(seq) if n >= co_config.max_user_frames: break - span.set_tag_str(f"_dd.code_origin.frames.{n}.file", str(frame_origin.resolve())) + span.set_tag_str(f"_dd.code_origin.frames.{n}.file", filename) span.set_tag_str(f"_dd.code_origin.frames.{n}.line", str(code.co_firstlineno)) # DEV: Without a function object we cannot infer the function # and any potential class name. From c212f45c1df66126cec9675a5869ad5aebce1cee Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:05:16 +0200 Subject: [PATCH 056/372] chore(asm): change deprecation logic for default settings (#11143) To avoid unknown source for default value on asm settings, change the deprecation code for _automatic_login_events_mode (but keeping the same logic) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/asm.py | 6 +++--- tests/telemetry/test_writer.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 0764b157d97..487045d27ff 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -102,7 +102,7 @@ class ASMConfig(Env): _auto_user_instrumentation_local_mode = Env.var( str, APPSEC.AUTO_USER_INSTRUMENTATION_MODE, - default="", + default=LOGIN_EVENTS_MODE.IDENT, parser=_parse_options([LOGIN_EVENTS_MODE.DISABLED, LOGIN_EVENTS_MODE.IDENT, LOGIN_EVENTS_MODE.ANON]), ) _auto_user_instrumentation_rc_mode: Optional[str] = None @@ -216,8 +216,8 @@ def __init__(self): # Is one click available? self._asm_can_be_enabled = APPSEC_ENV not in os.environ and tracer_config._remote_config_enabled # Only for deprecation phase - if self._auto_user_instrumentation_local_mode == "": - self._auto_user_instrumentation_local_mode = self._automatic_login_events_mode or LOGIN_EVENTS_MODE.IDENT + if self._automatic_login_events_mode and APPSEC.AUTO_USER_INSTRUMENTATION_MODE not in os.environ: + self._auto_user_instrumentation_local_mode = self._automatic_login_events_mode if not self._asm_libddwaf_available: self._asm_enabled = False self._asm_can_be_enabled = False diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index bcac256348f..ba49d0e3103 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -289,7 +289,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_API_SECURITY_SAMPLE_DELAY", "origin": "default", "value": 30.0}, {"name": "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING", "origin": "default", "value": ""}, {"name": "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED", "origin": "default", "value": True}, - {"name": "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE", "origin": "default", "value": ""}, + {"name": "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE", "origin": "default", "value": "identification"}, {"name": "DD_APPSEC_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_APPSEC_MAX_STACK_TRACES", "origin": "default", "value": 2}, {"name": "DD_APPSEC_MAX_STACK_TRACE_DEPTH", "origin": "default", "value": 32}, From 0f2c45d56b28cb55a5bbfd5db01a4bd49de5b1c5 Mon Sep 17 00:00:00 2001 From: Augusto de Oliveira Date: Thu, 24 Oct 2024 17:52:18 +0200 Subject: [PATCH 057/372] chore(ci): add env vars to control macrobenchmark warm up (#11123) [APMSP-1452 Investigate increase in instability in python/macrobenchmarks after wsgi to gunicorn change](https://datadoghq.atlassian.net/browse/APMSP-1452) Overview: - Add k6 environment variables to control warm up to Python macrobenchmarks. Please refer to https://github.com/DataDog/benchmarking-platform/pull/106 for a description of changes. Motivation: - `uwsgi` had issues that affected Python macrobenchmarks. We switched `uwsgi` for `gunicorn`. `gunicorn` brought problems of its own, namely latency spikes in the beginning of experiments. To avoid measuring initial spikes, a warm up before the actual experiments had to be added. - Please see detailed notes here: https://docs.google.com/document/d/19dZru9rYzqWMyeHow5Krlw27UvGj_VaR_CmA-35HFaM/edit?tab=t.8acydyg34pk0 Testing: - Compared results with those from two weeks ago (before `uwsgi` was substituted by `gunicorn` and before benchmarks were unstable). [See thorough comparison](https://docs.google.com/document/d/19dZru9rYzqWMyeHow5Krlw27UvGj_VaR_CmA-35HFaM/edit?tab=t.8acydyg34pk0#heading=h.p0lakz619cdw). - Ran smoke benchmarks after changes were made both in dd-trace-py and in benchmarking-platform. See [dashboard](https://ddstaging.datadoghq.com/dashboard/x79-z8u-xxh?fromUser=true&refresh_mode=paused&tpl_var_bp_branch%5B0%5D=igoragoli%2Fpython-macrobenchmarks-add-warmup&tpl_var_bp_commit_sha%5B0%5D=b7e2419f&tpl_var_ci_pipeline_id%5B0%5D=47156559&from_ts=1729611900000&to_ts=1729612800000&live=false). ## Checklist - [x] PR author has checked that all the criteria below are met - [x] The PR description includes an overview of the change - [x] The PR description articulates the motivation for the change - [x] The change includes tests OR the PR description describes a testing strategy - [x] The PR description notes risks associated with the change, if any - [x] Newly-added code is easy to change - [x] The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - [x] The change includes or references documentation updates if necessary - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/macrobenchmarks.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab/macrobenchmarks.yml b/.gitlab/macrobenchmarks.yml index 1aa3f2b6ecb..fb2f6cc5690 100644 --- a/.gitlab/macrobenchmarks.yml +++ b/.gitlab/macrobenchmarks.yml @@ -43,6 +43,12 @@ variables: DD_REMOTE_CONFIGURATION_ENABLED: "false" DD_INSTRUMENTATION_TELEMETRY_ENABLED: "false" + K6_OPTIONS_WARMUP_RATE: 40 + K6_OPTIONS_WARMUP_DURATION: 1m + K6_OPTIONS_WARMUP_GRACEFUL_STOP: 0s + K6_OPTIONS_WARMUP_PRE_ALLOCATED_VUS: 4 + K6_OPTIONS_WARMUP_MAX_VUS: 4 + K6_OPTIONS_NORMAL_OPERATION_RATE: 40 K6_OPTIONS_NORMAL_OPERATION_DURATION: 5m K6_OPTIONS_NORMAL_OPERATION_GRACEFUL_STOP: 1m From f26e7c4ce6e029147b657ef4daf2b758ac9d3410 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 24 Oct 2024 18:04:37 +0200 Subject: [PATCH 058/372] chore(iast): make the appsec_iast_aspects benchmark scenario work with previous versions (#11078) ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- benchmarks/appsec_iast_aspects/config.yaml | 1450 ++++++----------- benchmarks/appsec_iast_aspects/functions.py | 461 ++++++ benchmarks/appsec_iast_aspects/scenario.py | 49 +- .../aspects_benchmarks_simple_report.py | 55 + .../appsec/_iast/_taint_tracking/aspects.py | 68 +- 5 files changed, 1078 insertions(+), 1005 deletions(-) create mode 100644 benchmarks/appsec_iast_aspects/functions.py create mode 100644 benchmarks/bm/iast_utils/aspects_benchmarks_simple_report.py diff --git a/benchmarks/appsec_iast_aspects/config.yaml b/benchmarks/appsec_iast_aspects/config.yaml index c8eca9b8951..04c56704e52 100644 --- a/benchmarks/appsec_iast_aspects/config.yaml +++ b/benchmarks/appsec_iast_aspects/config.yaml @@ -1,956 +1,494 @@ -# This is autogenerated by the aspects_benchmarks_generate.py script -aspect_no_iast_do_add_and_uppercase: &aspect_no_iast_do_add_and_uppercase - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_add_and_uppercase" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_add_and_uppercase: - << : *aspect_no_iast_do_add_and_uppercase - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_add_re_compile: &aspect_no_iast_do_add_re_compile - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_add_re_compile" - args: [] - -aspect_iast_do_add_re_compile: - << : *aspect_no_iast_do_add_re_compile - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_capitalize: &aspect_no_iast_do_capitalize - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_capitalize" - args: [' fOobaR\t \n'] - -aspect_iast_do_capitalize: - << : *aspect_no_iast_do_capitalize - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_casefold: &aspect_no_iast_do_casefold - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_casefold" - args: [' fOobaR\t \n'] - -aspect_iast_do_casefold: - << : *aspect_no_iast_do_casefold - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_center: &aspect_no_iast_do_center - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_center" - args: ['foobar', 2] - -aspect_iast_do_center: - << : *aspect_no_iast_do_center - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_customspec_ascii: &aspect_no_iast_do_customspec_ascii - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_customspec_ascii" - args: [] - -aspect_iast_do_customspec_ascii: - << : *aspect_no_iast_do_customspec_ascii - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_encode: &aspect_no_iast_do_encode - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_encode" - args: ['foobar', 'utf-8', 'strict'] - -aspect_iast_do_encode: - << : *aspect_no_iast_do_encode - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_expandtabs: &aspect_no_iast_do_expandtabs - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_expandtabs" - args: [' fOobaR\t \n'] - -aspect_iast_do_expandtabs: - << : *aspect_no_iast_do_expandtabs - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_exporttype_member_format: &aspect_no_iast_do_exporttype_member_format - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_exporttype_member_format" - args: [] - -aspect_iast_do_exporttype_member_format: - << : *aspect_no_iast_do_exporttype_member_format - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_format_map: &aspect_no_iast_do_format_map - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_format_map" - args: ['foobar{baz}', {'baz': 'bar'}] - -aspect_iast_do_format_map: - << : *aspect_no_iast_do_format_map - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_fstring: &aspect_no_iast_do_fstring - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_fstring" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_fstring: - << : *aspect_no_iast_do_fstring - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_index: &aspect_no_iast_do_index - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_index" - args: ['foobar', 3] - -aspect_iast_do_index: - << : *aspect_no_iast_do_index - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_index_on_dict: &aspect_no_iast_do_index_on_dict - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_index_on_dict" - args: [{'fOobaR': 4, 3: 'foobar'}, 'fOobaR'] - -aspect_iast_do_index_on_dict: - << : *aspect_no_iast_do_index_on_dict - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_io_bytesio_read: &aspect_no_iast_do_io_bytesio_read - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_io_bytesio_read" - args: [' fOobaR\t \n'] - -aspect_iast_do_io_bytesio_read: - << : *aspect_no_iast_do_io_bytesio_read - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_io_stringio_read: &aspect_no_iast_do_io_stringio_read - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_io_stringio_read" - args: [' fOobaR\t \n'] - -aspect_iast_do_io_stringio_read: - << : *aspect_no_iast_do_io_stringio_read - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_join: &aspect_no_iast_do_join - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_join" - args: ['foobar', ['baz', 'pok']] - -aspect_iast_do_join: - << : *aspect_no_iast_do_join - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_ljust: &aspect_no_iast_do_ljust - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_ljust" - args: ['foobar', 2] - -aspect_iast_do_ljust: - << : *aspect_no_iast_do_ljust - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_lower: &aspect_no_iast_do_lower - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_lower" - args: [' fOobaR\t \n'] - -aspect_iast_do_lower: - << : *aspect_no_iast_do_lower - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_lstrip: &aspect_no_iast_do_lstrip - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_lstrip" - args: [' fOobaR\t \n'] - -aspect_iast_do_lstrip: - << : *aspect_no_iast_do_lstrip - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_modulo: &aspect_no_iast_do_modulo - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_modulo" - args: ['foobar%s', 'baz'] - -aspect_iast_do_modulo: - << : *aspect_no_iast_do_modulo - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_multiple_string_assigment: &aspect_no_iast_do_multiple_string_assigment - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_multiple_string_assigment" - args: [' fOobaR\t \n'] - -aspect_iast_do_multiple_string_assigment: - << : *aspect_no_iast_do_multiple_string_assigment - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_namedtuple: &aspect_no_iast_do_namedtuple - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_namedtuple" - args: [' fOobaR\t \n'] - -aspect_iast_do_namedtuple: - << : *aspect_no_iast_do_namedtuple - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_operator_add_inplace_3_params: &aspect_no_iast_do_operator_add_inplace_3_params - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_operator_add_inplace_3_params" - args: [' fOobaR\t \n', ' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_operator_add_inplace_3_params: - << : *aspect_no_iast_do_operator_add_inplace_3_params - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_operator_add_inplace_3_times: &aspect_no_iast_do_operator_add_inplace_3_times - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_operator_add_inplace_3_times" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_operator_add_inplace_3_times: - << : *aspect_no_iast_do_operator_add_inplace_3_times - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_operator_add_inplace_params: &aspect_no_iast_do_operator_add_inplace_params - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_operator_add_inplace_params" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_operator_add_inplace_params: - << : *aspect_no_iast_do_operator_add_inplace_params - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_operator_add_params: &aspect_no_iast_do_operator_add_params - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_operator_add_params" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_operator_add_params: - << : *aspect_no_iast_do_operator_add_params - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_partition: &aspect_no_iast_do_partition - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_partition" - args: ['foobar', 'o'] - -aspect_iast_do_partition: - << : *aspect_no_iast_do_partition - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_re_match_index: &aspect_no_iast_do_re_match_index - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_re_match_index" - args: ['(\\w+) (\\w+)', 'foo bar', 1] - -aspect_iast_do_re_match_index: - << : *aspect_no_iast_do_re_match_index - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_re_sub: &aspect_no_iast_do_re_sub - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_re_sub" - args: ['foobar', 'o', 'a', 1] - -aspect_iast_do_re_sub: - << : *aspect_no_iast_do_re_sub - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_replace: &aspect_no_iast_do_replace - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_replace" - args: ['foobar', 'o', 'a', 1] - -aspect_iast_do_replace: - << : *aspect_no_iast_do_replace - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_repr: &aspect_no_iast_do_repr - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_repr" - args: [' fOobaR\t \n'] - -aspect_iast_do_repr: - << : *aspect_no_iast_do_repr - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_sensitive_variables: &aspect_no_iast_do_sensitive_variables - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_sensitive_variables" - args: [' fOobaR\t \n'] - -aspect_iast_do_sensitive_variables: - << : *aspect_no_iast_do_sensitive_variables - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_slice: &aspect_no_iast_do_slice - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_slice" - args: ['foobar', 1, 3, 1] - -aspect_iast_do_slice: - << : *aspect_no_iast_do_slice - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_split_separator_and_maxsplit: &aspect_no_iast_do_split_separator_and_maxsplit - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_split_separator_and_maxsplit" - args: ['foo bar baz', ' ', 1] - -aspect_iast_do_split_separator_and_maxsplit: - << : *aspect_no_iast_do_split_separator_and_maxsplit - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_splitlines_keepends: &aspect_no_iast_do_splitlines_keepends - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_splitlines_keepends" - args: ['foo\nbar', False] - -aspect_iast_do_splitlines_keepends: - << : *aspect_no_iast_do_splitlines_keepends - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_str: &aspect_no_iast_do_str - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_str" - args: [' fOobaR\t \n'] - -aspect_iast_do_str: - << : *aspect_no_iast_do_str - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_string_assignment: &aspect_no_iast_do_string_assignment - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_string_assignment" - args: [' fOobaR\t \n'] - -aspect_iast_do_string_assignment: - << : *aspect_no_iast_do_string_assignment - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_stringio_init_and_read: &aspect_no_iast_do_stringio_init_and_read - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_stringio_init_and_read" - args: [' fOobaR\t \n'] - -aspect_iast_do_stringio_init_and_read: - << : *aspect_no_iast_do_stringio_init_and_read - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_swapcase: &aspect_no_iast_do_swapcase - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_swapcase" - args: [' fOobaR\t \n'] - -aspect_iast_do_swapcase: - << : *aspect_no_iast_do_swapcase - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_title: &aspect_no_iast_do_title - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_title" - args: [' fOobaR\t \n'] - -aspect_iast_do_title: - << : *aspect_no_iast_do_title - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_translate: &aspect_no_iast_do_translate - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_translate" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_translate: - << : *aspect_no_iast_do_translate - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_tuple_string_assignment: &aspect_no_iast_do_tuple_string_assignment - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_tuple_string_assignment" - args: ['foo'] - -aspect_iast_do_tuple_string_assignment: - << : *aspect_no_iast_do_tuple_string_assignment - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_upper: &aspect_no_iast_do_upper - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_upper" - args: [' fOobaR\t \n'] - -aspect_iast_do_upper: - << : *aspect_no_iast_do_upper - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_zfill: &aspect_no_iast_do_zfill - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods" - function_name: "do_zfill" - args: ['foobar', 10] - -aspect_iast_do_zfill: - << : *aspect_no_iast_do_zfill - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_fmt_value: &aspect_no_iast_do_fmt_value - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods_py3" - function_name: "do_fmt_value" - args: [' fOobaR\t \n'] - -aspect_iast_do_fmt_value: - << : *aspect_no_iast_do_fmt_value - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_repr_fstring: &aspect_no_iast_do_repr_fstring - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.str_methods_py3" - function_name: "do_repr_fstring" - args: [' fOobaR\t \n'] - -aspect_iast_do_repr_fstring: - << : *aspect_no_iast_do_repr_fstring - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_basename: &aspect_no_iast_do_os_path_basename - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_basename" - args: ['foo/bar'] - -aspect_iast_do_os_path_basename: - << : *aspect_no_iast_do_os_path_basename - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_dirname: &aspect_no_iast_do_os_path_dirname - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_dirname" - args: [' fOobaR\t \n'] - -aspect_iast_do_os_path_dirname: - << : *aspect_no_iast_do_os_path_dirname - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_join: &aspect_no_iast_do_os_path_join - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_join" - args: [' fOobaR\t \n', ' fOobaR\t \n'] - -aspect_iast_do_os_path_join: - << : *aspect_no_iast_do_os_path_join - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_normcase: &aspect_no_iast_do_os_path_normcase - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_normcase" - args: [' fOobaR\t \n'] - -aspect_iast_do_os_path_normcase: - << : *aspect_no_iast_do_os_path_normcase - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_split: &aspect_no_iast_do_os_path_split - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_split" - args: ['foo/bar'] - -aspect_iast_do_os_path_split: - << : *aspect_no_iast_do_os_path_split - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_splitdrive: &aspect_no_iast_do_os_path_splitdrive - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_splitdrive" - args: ['foo/bar'] - -aspect_iast_do_os_path_splitdrive: - << : *aspect_no_iast_do_os_path_splitdrive - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -aspect_no_iast_do_os_path_splitext: &aspect_no_iast_do_os_path_splitext - iast_enabled: 0 - processes: 10 - loops: 1 - values: 6 - warmups: 1 - mod_original_name: "bm.iast_fixtures.module_functions" - function_name: "do_os_path_splitext" - args: ['foo/bar.txt'] - -aspect_iast_do_os_path_splitext: - << : *aspect_no_iast_do_os_path_splitext - processes: 10 - loops: 1 - values: 6 - warmups: 1 - iast_enabled: 1 - -# end content autogenerated by aspects_benchmarks_generate.py +add_aspect: &add_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_add_aspect" + +add_noaspect: + <<: *add_aspect + function_name: "add_noaspect" + +add_inplace_aspect: &add_inplace_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_add_inplace_aspect" + +add_inplace_noaspect: + <<: *add_inplace_aspect + function_name: "add_inplace_noaspect" + +bytearray_aspect: &bytearray_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_bytearray_aspect" + +bytearray_noaspect: + <<: *bytearray_aspect + function_name: "bytearray_noaspect" + +bytearray_extend_aspect: &bytearray_extend_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_bytearray_extend_aspect" + +bytearray_extend_noaspect: + <<: *bytearray_extend_aspect + function_name: "bytearray_extend_noaspect" + +bytes_aspect: &bytes_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_bytes_aspect" + +bytes_noaspect: + <<: *bytes_aspect + function_name: "bytes_noaspect" + +bytesio_aspect: &bytesio_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_bytesio_aspect" + +bytesio_noaspect: + <<: *bytesio_aspect + function_name: "bytesio_noaspect" + +capitalize_aspect: &capitalize_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_capitalize_aspect" + +capitalize_noaspect: + <<: *capitalize_aspect + function_name: "capitalize_noaspect" + +casefold_aspect: &casefold_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_casefold_aspect" + +casefold_noaspect: + <<: *casefold_aspect + function_name: "casefold_noaspect" + +decode_aspect: &decode_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_decode_aspect" + +decode_noaspect: + <<: *decode_aspect + function_name: "decode_noaspect" + +encode_aspect: &encode_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_encode_aspect" + +encode_noaspect: + <<: *encode_aspect + function_name: "encode_noaspect" + +format_aspect: &format_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_format_aspect" + +format_noaspect: + <<: *format_aspect + function_name: "format_noaspect" + +format_map_aspect: &format_map_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_format_map_aspect" + +format_map_noaspect: + <<: *format_map_aspect + function_name: "format_map_noaspect" + +index_aspect: &index_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_index_aspect" + +index_noaspect: + <<: *index_aspect + function_name: "index_noaspect" + +join_aspect: &join_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_join_aspect" + +join_noaspect: + <<: *join_aspect + function_name: "join_noaspect" + +lower_aspect: &lower_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_lower_aspect" + +lower_noaspect: + <<: *lower_aspect + function_name: "lower_noaspect" + +ljust_aspect: &ljust_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ljust_aspect" + +ljust_noaspect: + <<: *ljust_aspect + function_name: "ljust_noaspect" + +modulo_aspect: &modulo_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_modulo_aspect" + +modulo_noaspect: + <<: *modulo_aspect + function_name: "modulo_noaspect" + +ospathbasename_aspect: &ospathbasename_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathbasename_aspect" + +ospathbasename_noaspect: + <<: *ospathbasename_aspect + function_name: "ospathbasename_noaspect" + +ospathdirname_aspect: &ospathdirname_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathdirname_aspect" + +ospathdirname_noaspect: + <<: *ospathdirname_aspect + function_name: "ospathdirname_noaspect" + +ospathjoin_aspect: &ospathjoin_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathjoin_aspect" + +ospathjoin_noaspect: + <<: *ospathjoin_aspect + function_name: "ospathjoin_noaspect" + +ospathnormcase_aspect: &ospathnormcase_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathnormcase_aspect" + +ospathnormcase_noaspect: + <<: *ospathnormcase_aspect + function_name: "ospathnormcase_noaspect" + +ospathsplit_aspect: &ospathsplit_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathsplit_aspect" + +ospathsplit_noaspect: + <<: *ospathsplit_aspect + function_name: "ospathsplit_noaspect" + +ospathsplitdrive_aspect: &ospathsplitdrive_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathsplitdrive_aspect" + +ospathsplitdrive_noaspect: + <<: *ospathsplitdrive_aspect + function_name: "ospathsplitdrive_noaspect" + +ospathsplitext_aspect: &ospathsplitext_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_ospathsplitext_aspect" + +ospathsplitext_noaspect: + <<: *ospathsplitext_aspect + function_name: "ospathsplitext_noaspect" + +re_expand_aspect: &re_expand_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_expand_aspect" + +re_expand_noaspect: + <<: *re_expand_aspect + function_name: "re_expand_noaspect" + +re_findall_aspect: &re_findall_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_findall_aspect" + +re_findall_noaspect: + <<: *re_findall_aspect + function_name: "re_findall_noaspect" + +re_finditer_aspect: &re_finditer_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_finditer_aspect" + +re_finditer_noaspect: + <<: *re_finditer_aspect + function_name: "re_finditer_noaspect" + +re_fullmatch_aspect: &re_fullmatch_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_fullmatch_aspect" + +re_fullmatch_noaspect: + <<: *re_fullmatch_aspect + function_name: "re_fullmatch_noaspect" + +re_group_aspect: &re_group_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_group_aspect" + +re_group_noaspect: + <<: *re_group_aspect + function_name: "re_group_noaspect" + +re_groups_aspect: &re_groups_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_groups_aspect" + +re_groups_noaspect: + <<: *re_groups_aspect + function_name: "re_groups_noaspect" + +re_match_aspect: &re_match_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_match_aspect" + +re_match_noaspect: + <<: *re_match_aspect + function_name: "re_match_noaspect" + +re_search_aspect: &re_search_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_search_aspect" + +re_search_noaspect: + <<: *re_search_aspect + function_name: "re_search_noaspect" + +re_sub_aspect: &re_sub_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_sub_aspect" + +re_sub_noaspect: + <<: *re_sub_aspect + function_name: "re_sub_noaspect" + +re_subn_aspect: &re_subn_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_re_subn_aspect" + +re_subn_noaspect: + <<: *re_subn_aspect + function_name: "re_subn_noaspect" + +replace_aspect: &replace_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_replace_aspect" + +replace_noaspect: + <<: *replace_aspect + function_name: "replace_noaspect" + +repr_aspect: &repr_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_repr_aspect" + +repr_noaspect: + <<: *repr_aspect + function_name: "repr_noaspect" + +rsplit_aspect: &rsplit_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_rsplit_aspect" + +rsplit_noaspect: + <<: *rsplit_aspect + function_name: "rsplit_noaspect" + +slice_aspect: &slice_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_slice_aspect" + +slice_noaspect: + <<: *slice_aspect + function_name: "slice_noaspect" + +split_aspect: &split_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_split_aspect" + +split_noaspect: + <<: *split_aspect + function_name: "split_noaspect" + +splitlines_aspect: &splitlines_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_splitlines_aspect" + +splitlines_noaspect: + <<: *splitlines_aspect + function_name: "splitlines_noaspect" + +stringio_aspect: &stringio_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_stringio_aspect" + +stringio_noaspect: + <<: *stringio_aspect + function_name: "stringio_noaspect" + +swapcase_aspect: &swapcase_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_swapcase_aspect" + +swapcase_noaspect: + <<: *swapcase_aspect + function_name: "swapcase_noaspect" + +title_aspect: &title_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_title_aspect" + +title_noaspect: + <<: *title_aspect + function_name: "title_noaspect" + +translate_aspect: &translate_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_translate_aspect" + +translate_noaspect: + <<: *translate_aspect + function_name: "translate_noaspect" + +upper_aspect: &upper_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_upper_aspect" + +upper_noaspect: + <<: *upper_aspect + function_name: "upper_noaspect" diff --git a/benchmarks/appsec_iast_aspects/functions.py b/benchmarks/appsec_iast_aspects/functions.py new file mode 100644 index 00000000000..1ec76f7b8da --- /dev/null +++ b/benchmarks/appsec_iast_aspects/functions.py @@ -0,0 +1,461 @@ +import os +import re + +import _io + +import ddtrace._version as version + + +# Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found +# which will make the aspect benchmark fail but not the entire benchmark +symbols = [ + "add_aspect", + "add_inplace_aspect", + "bytearray_aspect", + "bytearray_extend_aspect", + "bytes_aspect", + "bytesio_aspect", + "capitalize_aspect", + "casefold_aspect", + "decode_aspect", + "encode_aspect", + "format_aspect", + "format_map_aspect", + "index_aspect", + "join_aspect", + "ljust_aspect", + "lower_aspect", + "modulo_aspect", + "ospathbasename_aspect", + "ospathdirname_aspect", + "ospathjoin_aspect", + "ospathnormcase_aspect", + "ospathsplit_aspect", + "ospathsplitdrive_aspect", + "ospathsplitext_aspect", + "re_expand_aspect", + "re_findall_aspect", + "re_finditer_aspect", + "re_fullmatch_aspect", + "re_group_aspect", + "re_groups_aspect", + "re_match_aspect", + "re_search_aspect", + "re_sub_aspect", + "re_subn_aspect", + "replace_aspect", + "repr_aspect", + "rsplit_aspect", + "slice_aspect", + "split_aspect", + "split_aspect", + "splitlines_aspect", + "str_aspect", + "stringio_aspect", + "swapcase_aspect", + "title_aspect", + "translate_aspect", + "upper_aspect", +] + +notfound_symbols = [] + +for symbol in symbols: + try: + # Dynamically import the symbol from the module and assign to globals + globals()[symbol] = __import__("ddtrace.appsec._iast._taint_tracking.aspects", fromlist=[symbol]).__dict__[ + symbol + ] + except (ImportError, KeyError): + # If the symbol is not found, assign None and print a warning + globals()[symbol] = None + notfound_symbols.append(symbol) + # print(f"Warning: {symbol} not found in the current version") + +if notfound_symbols: + print("Warning: symbols not found in the tested version [%s]: %s" % (version.version, str(notfound_symbols))) + + +def iast_add_aspect(): + return add_aspect(3, 4) # noqa: F821 + + +def add_noaspect(): + return 3 + 4 + + +def iast_add_inplace_aspect(): + return add_inplace_aspect(42, 1) # noqa: F821 + + +def add_inplace_noaspect(): + a = 42 + a += 1 + return a + + +def iast_bytearray_aspect(): + return bytearray_aspect(bytearray, 0, b"test") # noqa: F821 + + +def bytearray_noaspect(): + return bytearray(b"test") + + +def iast_bytearray_extend_aspect(): + ba = bytearray(b"hello") + bytearray_extend_aspect(None, 0, ba, b" world") # noqa: F821 + + +def bytearray_extend_noaspect(): + ba = bytearray(b"hello") + ba.extend(b" world") + + +def iast_bytes_aspect(): + return bytes_aspect(bytes, 0, "hello", "utf-8") # noqa: F821 + + +def bytes_noaspect(): + return bytes("hello", "utf-8") + + +def iast_bytesio_aspect(): + x = bytesio_aspect(None, 0, b"data") # noqa: F821 + return x.read() + + +def bytesio_noaspect(): + x = _io.BytesIO(b"data") + return x.read() + + +def iast_capitalize_aspect(): + return capitalize_aspect(str, 0, "example") # noqa: F821 + + +def capitalize_noaspect(): + return "example".capitalize() + + +def iast_casefold_aspect(): + return casefold_aspect(str, 0, "EXAMPLE") # noqa: F821 + + +def casefold_noaspect(): + return "EXAMPLE".casefold() + + +def iast_decode_aspect(): + return decode_aspect(str, 0, b"hello", "utf-8") # noqa: F821 + + +def decode_noaspect(): + return b"hello".decode("utf-8") + + +def iast_encode_aspect(): + return encode_aspect(bytes, 0, "hello", "utf-8") # noqa: F821 + + +def encode_noaspect(): + return "hello".encode("utf-8") + + +def iast_format_aspect(): + return format_aspect(None, 1, "Hello, {}!", "World") # noqa: F821 + + +def format_noaspect(): + return "Hello, {}!".format("World") + + +def iast_format_map_aspect(): + return format_map_aspect(None, 1, "{greeting}, World!", {"greeting": "Hello"}) # noqa: F821 + + +def format_map_noaspect(): + return "{greeting}, World!".format_map({"greeting": "Hello"}) + + +def iast_index_aspect(): + return index_aspect("example", 3) # noqa: F821 + + +def index_noaspect(): + return "example"[3] + + +def iast_join_aspect(): + return join_aspect(None, 1, ", ", ["one", "two", "three"]) # noqa: F821 + + +def join_noaspect(): + return ", ".join(["one", "two", "three"]) + + +def iast_lower_aspect(): + return lower_aspect(None, 1, "EXAMPLE") # noqa: F821 + + +def lower_noaspect(): + return "EXAMPLE".lower() + + +def iast_ljust_aspect(): + return ljust_aspect(None, 1, "example", 10) # noqa: F821 + + +def ljust_noaspect(): + return "example".ljust(10) + + +def iast_modulo_aspect(): + return modulo_aspect("hello %s", "foo") # noqa: F821 + + +def modulo_noaspect(): + return "{} {}".format("hello", "world") + + +def iast_ospathbasename_aspect(): + return ospathbasename_aspect("/path/to/file") # noqa: F821 + + +def ospathbasename_noaspect(): + return os.path.basename("/path/to/file") + + +def iast_ospathdirname_aspect(): + return ospathdirname_aspect("/path/to/file") # noqa: F821 + + +def ospathdirname_noaspect(): + return os.path.dirname("/path/to/file") + + +def iast_ospathjoin_aspect(): + return ospathjoin_aspect("/path", "to", "file") # noqa: F821 + + +def ospathjoin_noaspect(): + return os.path.join("/path", "to", "file") + + +def iast_ospathnormcase_aspect(): + return ospathnormcase_aspect("example") # noqa: F821 + + +def ospathnormcase_noaspect(): + return os.path.normcase("example") + + +def iast_ospathsplit_aspect(): + return ospathsplit_aspect("/path/to/file") # noqa: F821 + + +def ospathsplit_noaspect(): + return os.path.split("/path/to/file") + + +def iast_ospathsplitdrive_aspect(): + return ospathsplitdrive_aspect("/path/to/file") # noqa: F821 + + +def ospathsplitdrive_noaspect(): + return os.path.splitdrive("/path/to/file") + + +def iast_ospathsplitext_aspect(): + return ospathsplitext_aspect("/path/to/file") # noqa: F821 + + +def ospathsplitext_noaspect(): + return os.path.splitext("/path/to/file") + + +def iast_re_sub_aspect(): + return re_sub_aspect(None, 1, re.compile("/"), "_", "foo/bar") # noqa: F821 + + +def re_sub_noaspect(): + return re.sub("/", "_", "foo/bar") + + +def iast_rsplit_aspect(): + return rsplit_aspect(None, 0, "foo bar baz") # noqa: F821 + + +def rsplit_noaspect(): + return "foo bar baz".rsplit() + + +def iast_splitlines_aspect(): + return splitlines_aspect(None, 0, "line1\nline2\nline3") # noqa: F821 + + +def splitlines_noaspect(): + return "line1\nline2\nline3".splitlines() + + +def iast_str_aspect(): + return str_aspect(str, 0, 42) # noqa: F821 + + +def str_noaspect(): + return str(42) + + +def iast_stringio_aspect(): + io = stringio_aspect(None, 0, "data") # noqa: F821 + return io.read() + + +def stringio_noaspect(): + io = _io.StringIO("data") + return io.read() + + +def iast_repr_aspect(): + return repr_aspect(None, 0, 42) # noqa: F821 + + +def repr_noaspect(): + return repr(42) + + +def iast_slice_aspect(): + return slice_aspect( # noqa: F821 + "example", + 1, + 3, + 1, + ) + + +def slice_noaspect(): + return "example"[1:3:1] + + +def iast_replace_aspect(): + return replace_aspect(None, 1, "example", "example", "foo") # noqa: F821 + + +def replace_noaspect(): + return "example".replace("example", "foo") + + +def iast_re_subn_aspect(): + return re_subn_aspect(None, 1, re.compile("/"), "_", "foo/bar") # noqa: F821 + + +def re_subn_noaspect(): + return re.subn("/", "_", "foo/bar") + + +def iast_re_search_aspect(): + return re_search_aspect(None, 1, re.compile("foo"), "foo bar") # noqa: F821 + + +def re_search_noaspect(): + return re.search("foo", "foo bar") + + +def iast_re_match_aspect(): + return re_match_aspect(None, 1, re.compile("foo"), "foo bar") # noqa: F821 + + +def re_match_noaspect(): + return re.match("foo", "foo bar") + + +def iast_re_groups_aspect(): + return re_groups_aspect(None, 0, re.match(r"(\w+) (\w+)", "Hello World")) # noqa: F821 + + +def re_groups_noaspect(): + return re.match(r"(\w+) (\w+)", "Hello World").groups() + + +def iast_re_group_aspect(): + return re_group_aspect(None, 0, re.match(r"(\w+) (\w+)", "Hello World")) # noqa: F821 + + +def re_group_noaspect(): + return re.match(r"(\w+) (\w+)", "Hello World").group() + + +def iast_re_fullmatch_aspect(): + return re_fullmatch_aspect(None, 1, re.compile("foo"), "foo") # noqa: F821 + + +def re_fullmatch_noaspect(): + return re.fullmatch("foo", "foo") + + +def iast_re_finditer_aspect(): + return re_finditer_aspect(None, 1, re.compile("foo"), "foo bar foo") # noqa: F821 + + +def re_finditer_noaspect(): + return re.finditer("foo", "foo bar foo") + + +def iast_re_findall_aspect(): + return re_findall_aspect(None, 1, re.compile("foo"), "foo bar foo") # noqa: F821 + + +def re_findall_noaspect(): + return re.findall("foo", "foo bar foo") + + +def iast_re_expand_aspect(): + re_obj = re.compile(r"(\w+) (\w+)") + match = re.match(re_obj, "Hello World") + return re_expand_aspect(None, 1, match, "Salute: \\1 Subject: \\2") # noqa: F821 + + +def re_expand_noaspect(): + re_obj = re.compile(r"(\w+) (\w+)") + match = re.match(re_obj, "Hello World") + return match.expand("Salute: \\1 Subject: \\2") + + +def iast_upper_aspect(): + return upper_aspect(None, 1, "example") # noqa: F821 + + +def upper_noaspect(): + return "example".upper() + + +def iast_translate_aspect(): + return translate_aspect(None, 1, "example", {101: 105}) # noqa: F821 + + +def translate_noaspect(): + return "example".translate({101: 105}) + + +def iast_title_aspect(): + return title_aspect(None, 1, "hello world") # noqa: F821 + + +def title_noaspect(): + return "hello world".title() + + +def iast_swapcase_aspect(): + return swapcase_aspect(None, 1, "Hello World") # noqa: F821 + + +def swapcase_noaspect(): + return "Hello World".swapcase() + + +def iast_split_aspect(): + return split_aspect(None, 1, "foo bar baz") # noqa: F821 + + +def split_noaspect(): + return "foo bar baz".split() diff --git a/benchmarks/appsec_iast_aspects/scenario.py b/benchmarks/appsec_iast_aspects/scenario.py index 8a03d1a5c14..145b43f1633 100644 --- a/benchmarks/appsec_iast_aspects/scenario.py +++ b/benchmarks/appsec_iast_aspects/scenario.py @@ -1,48 +1,43 @@ -# import base64 -import ast -import importlib -import types - import bm from bm.utils import override_env -from ddtrace.appsec._iast import oce -from ddtrace.appsec._iast._ast.ast_patching import astpatch_module -from ddtrace.appsec._iast._iast_request_context import end_iast_context -from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled -from ddtrace.appsec._iast._iast_request_context import start_iast_context +with override_env({"DD_IAST_ENABLED": "True"}): + # from ddtrace.appsec._iast import oce + try: + # 2.15+ + from ddtrace.appsec._iast._iast_request_context import end_iast_context + from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled + from ddtrace.appsec._iast._iast_request_context import start_iast_context + except ImportError: + # Pre 2.15 + from ddtrace.appsec._iast._taint_tracking import create_context as start_iast_context + from ddtrace.appsec._iast._taint_tracking import reset_context as end_iast_context -# Copypasted here from tests.iast.aspects.conftest since the benchmarks can't access tests.* -def _iast_patched_module(module_name): - module = importlib.import_module(module_name) - module_path, patched_source = astpatch_module(module) - compiled_code = compile(patched_source, module_path, "exec") - module_changed = types.ModuleType(module_name) - exec(compiled_code, module_changed.__dict__) - return module_changed + set_iast_request_enabled = lambda x: None # noqa: E731 def _start_iast_context_and_oce(): - oce.reconfigure() - oce.acquire_request(None) + # oce.reconfigure() + # oce.acquire_request(None) start_iast_context() set_iast_request_enabled(True) def _end_iast_context_and_oce(): end_iast_context() - oce.release_request() + # oce.release_request() + + +with override_env({"DD_IAST_ENABLED": "True"}): + import functions class IAST_Aspects(bm.Scenario): iast_enabled: bool - mod_original_name: str function_name: str - args: str def run(self): - args = ast.literal_eval(self.args) if self.iast_enabled: with override_env({"DD_IAST_ENABLED": "True"}): _start_iast_context_and_oce() @@ -51,12 +46,10 @@ def _(loops): for _ in range(loops): if self.iast_enabled: with override_env({"DD_IAST_ENABLED": "True"}): - module_patched = _iast_patched_module(self.mod_original_name) + getattr(functions, self.function_name)() - getattr(module_patched, self.function_name)(*args) else: - module_unpatched = importlib.import_module(self.mod_original_name) - getattr(module_unpatched, self.function_name)(*args) + getattr(functions, self.function_name)() yield _ if self.iast_enabled: diff --git a/benchmarks/bm/iast_utils/aspects_benchmarks_simple_report.py b/benchmarks/bm/iast_utils/aspects_benchmarks_simple_report.py new file mode 100644 index 00000000000..152362fd262 --- /dev/null +++ b/benchmarks/bm/iast_utils/aspects_benchmarks_simple_report.py @@ -0,0 +1,55 @@ +import json +import os + + +def main(): + artifacts_dir = "../../../artifacts" + # find the subdir in artifacts_dir with the latest timestamp + subdirs = [d for d in os.listdir(artifacts_dir) if os.path.isdir(os.path.join(artifacts_dir, d))] + latest_subdir = max(subdirs, key=lambda d: os.path.getmtime(os.path.join(artifacts_dir, d))) + latest_subdir_path = os.path.join(artifacts_dir, latest_subdir) + print(latest_subdir_path) + aspects_subdir = os.path.join(latest_subdir_path, "appsec_iast_aspects") + versions = [d for d in os.listdir(aspects_subdir) if os.path.isdir(os.path.join(aspects_subdir, d))] + print(versions) + + data_dict = {} + + for version in versions: + version_dir = os.path.join(aspects_subdir, version) + with open(os.path.join(version_dir, "results.json")) as f: + data = json.load(f) + for item in data["benchmarks"]: + name = item["metadata"]["name"] + + sum_time = 0.0 + count = 0 + + for run in item["runs"]: + for value in run["values"]: + sum_time += float(value) + count += 1 + + avg_time = sum_time / count + if name not in data_dict: + data_dict[name] = {} + + # time is in seconds, convert to nanoseconds + data_dict[name][version] = avg_time * 1e9 + + for aspect in data_dict: + if "noaspect" in aspect: + continue + + try: + print( + "{}: \n\t{}: {}\n\t{}: {}".format( + aspect, versions[0], data_dict[aspect][versions[0]], versions[1], data_dict[aspect][versions[1]] + ) + ) + except KeyError: + pass + + +if __name__ == "__main__": + main() diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index c009e1be1fb..d70dc76449c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -56,51 +56,77 @@ TEXT_TYPES = Union[str, bytes, bytearray] +_extend_aspect = aspects.extend_aspect +_join_aspect = aspects.join_aspect add_aspect = aspects.add_aspect add_inplace_aspect = aspects.add_inplace_aspect -_extend_aspect = aspects.extend_aspect index_aspect = aspects.index_aspect -_join_aspect = aspects.join_aspect -slice_aspect = aspects.slice_aspect modulo_aspect = aspects.modulo_aspect -split_aspect = _aspect_split -rsplit_aspect = _aspect_rsplit -splitlines_aspect = _aspect_splitlines -str_aspect = aspects.str_aspect -ospathjoin_aspect = _aspect_ospathjoin ospathbasename_aspect = _aspect_ospathbasename ospathdirname_aspect = _aspect_ospathdirname +ospathjoin_aspect = _aspect_ospathjoin +ospathnormcase_aspect = _aspect_ospathnormcase ospathsplit_aspect = _aspect_ospathsplit -ospathsplitext_aspect = _aspect_ospathsplitext ospathsplitdrive_aspect = _aspect_ospathsplitdrive +ospathsplitext_aspect = _aspect_ospathsplitext ospathsplitroot_aspect = _aspect_ospathsplitroot -ospathnormcase_aspect = _aspect_ospathnormcase +rsplit_aspect = _aspect_rsplit +slice_aspect = aspects.slice_aspect +split_aspect = _aspect_split +splitlines_aspect = _aspect_splitlines +str_aspect = aspects.str_aspect __all__ = [ + "_aspect_rsplit", + "_aspect_split", + "_aspect_splitlines", "add_aspect", "add_inplace_aspect", - "str_aspect", + "bytearray_aspect", "bytearray_extend_aspect", + "bytes_aspect", + "bytesio_aspect", + "capitalize_aspect", + "casefold_aspect", "decode_aspect", "encode_aspect", - "re_sub_aspect", - "ospathjoin_aspect", - "_aspect_split", - "split_aspect", - "_aspect_rsplit", - "rsplit_aspect", + "format_aspect", + "format_map_aspect", + "index_aspect", + "join_aspect", + "ljust_aspect", + "lower_aspect", "modulo_aspect", - "_aspect_splitlines", - "splitlines_aspect", "ospathbasename_aspect", "ospathdirname_aspect", + "ospathjoin_aspect", "ospathnormcase_aspect", "ospathsplit_aspect", - "ospathsplitext_aspect", "ospathsplitdrive_aspect", + "ospathsplitext_aspect", "ospathsplitroot_aspect", - "bytesio_aspect", + "re_expand_aspect", + "re_findall_aspect", + "re_finditer_aspect", + "re_fullmatch_aspect", + "re_group_aspect", + "re_groups_aspect", + "re_match_aspect", + "re_search_aspect", + "re_sub_aspect", + "re_subn_aspect", + "replace_aspect", + "repr_aspect", + "rsplit_aspect", + "slice_aspect", + "split_aspect", + "splitlines_aspect", + "str_aspect", "stringio_aspect", + "swapcase_aspect", + "title_aspect", + "translate_aspect", + "upper_aspect", ] From ef77e3fa4fc0ecc1ef590c73f2dac0bebd424a1a Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:52:52 -0400 Subject: [PATCH 059/372] chore(tracing): add kafka cluster id to spans and DSM metrics (#11061) Adds Kafka cluster ID as a span tag and within DSM metrics. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/services.yml | 22 ++++----- ddtrace/contrib/internal/kafka/patch.py | 26 ++++++++++- ddtrace/ext/kafka.py | 1 + ddtrace/internal/datastreams/kafka.py | 19 ++++++-- docker-compose.yml | 29 +++++++----- tests/contrib/kafka/meta.properties | 5 ++ tests/contrib/kafka/test_kafka.py | 46 ++++++++++++++++--- ...a.test_kafka.test_analytics_with_rate.json | 1 + ...est_kafka.test_analytics_without_rate.json | 1 + ....contrib.kafka.test_kafka.test_commit.json | 2 + ...st_commit_with_consume_single_message.json | 2 + ...commit_with_consume_with_error[False].json | 1 + ...t_with_consume_with_multiple_messages.json | 3 ++ ...ka.test_kafka.test_commit_with_offset.json | 2 + ...kafka.test_commit_with_only_async_arg.json | 2 + ....kafka.test_kafka.test_message[False].json | 1 + ...b.kafka.test_kafka.test_message[True].json | 1 + ...span_service_and_operation[None-None].json | 2 + ...pan_service_and_operation[None-mysvc].json | 2 + ...d_span_service_and_operation[v0-None].json | 2 + ..._span_service_and_operation[v0-mysvc].json | 2 + ...d_span_service_and_operation[v1-None].json | 2 + ..._span_service_and_operation[v1-mysvc].json | 2 + ...afka.test_kafka.test_service_override.json | 2 + ...t_kafka.test_service_override_env_var.json | 2 + 25 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 tests/contrib/kafka/meta.properties diff --git a/.gitlab/services.yml b/.gitlab/services.yml index daefb0986e3..3adcb973e89 100644 --- a/.gitlab/services.yml +++ b/.gitlab/services.yml @@ -29,20 +29,20 @@ name: registry.ddbuild.io/redis:7.0.7 alias: redis kafka: - name: registry.ddbuild.io/images/mirror/bitnami/kafka:3.4.1 + name: registry.ddbuild.io/images/mirror/apache/kafka:3.8.0 alias: kafka variables: ALLOW_PLAINTEXT_LISTENER: yes - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:29092 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:29092 - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT - KAFKA_CFG_BROKER_ID: 1 - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_CFGOFFSETS_TOPIC_REPLICATION_FACTOR: 1 - KAFKA_CFG_NODE_ID: 1 - KAFKA_CFG_PROCESS_ROLES: controller,broker - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 - KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:29092 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_NODE_ID: 1 + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + CLUSTER_ID: 5L6g3nShT-eMCtK--X86sw + KAFKA_PROCESS_ROLES: controller,broker + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER httpbin_local: name: registry.ddbuild.io/images/mirror/mccutchen/go-httpbin:v2.14.1 alias: httpbin-local diff --git a/ddtrace/contrib/internal/kafka/patch.py b/ddtrace/contrib/internal/kafka/patch.py index 74a65e4c84c..225c0f82877 100644 --- a/ddtrace/contrib/internal/kafka/patch.py +++ b/ddtrace/contrib/internal/kafka/patch.py @@ -171,7 +171,13 @@ def traced_produce(func, instance, args, kwargs): service=trace_utils.ext_service(pin, config.kafka), span_type=SpanTypes.WORKER, ) as span: + cluster_id = _get_cluster_id(instance, topic) + core.set_item("kafka_cluster_id", cluster_id) + if cluster_id: + span.set_tag_str(kafkax.CLUSTER_ID, cluster_id) + core.dispatch("kafka.produce.start", (instance, args, kwargs, isinstance(instance, _SerializingProducer), span)) + span.set_tag_str(MESSAGING_SYSTEM, kafkax.SERVICE) span.set_tag_str(COMPONENT, config.kafka.integration_name) span.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) @@ -246,15 +252,20 @@ def _instrument_message(messages, pin, start_ns, instance, err): ) as span: # reset span start time to before function call span.start_ns = start_ns + cluster_id = None for message in messages: if message is not None and first_message is not None: + cluster_id = _get_cluster_id(instance, str(first_message.topic())) + core.set_item("kafka_cluster_id", cluster_id) core.set_item("kafka_topic", str(first_message.topic())) core.dispatch("kafka.consume.start", (instance, first_message, span)) span.set_tag_str(MESSAGING_SYSTEM, kafkax.SERVICE) span.set_tag_str(COMPONENT, config.kafka.integration_name) span.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) + if cluster_id: + span.set_tag_str(kafkax.CLUSTER_ID, cluster_id) span.set_tag_str(kafkax.RECEIVED_MESSAGE, str(first_message is not None)) span.set_tag_str(kafkax.GROUP_ID, instance._group_id) if first_message is not None: @@ -293,7 +304,6 @@ def traced_commit(func, instance, args, kwargs): return func(*args, **kwargs) core.dispatch("kafka.commit.start", (instance, args, kwargs)) - return func(*args, **kwargs) @@ -310,3 +320,17 @@ def serialize_key(instance, topic, key, headers): else: log.warning("Failed to set Kafka Consumer key tag, no method available to serialize key: %s", str(key)) return None + + +def _get_cluster_id(instance, topic): + if instance and getattr(instance, "_dd_cluster_id", None): + return instance._dd_cluster_id + + if getattr(instance, "list_topics", None) is None: + return None + + cluster_metadata = instance.list_topics(topic=topic) + if cluster_metadata and getattr(cluster_metadata, "cluster_id", None): + instance._dd_cluster_id = cluster_metadata.cluster_id + return cluster_metadata.cluster_id + return None diff --git a/ddtrace/ext/kafka.py b/ddtrace/ext/kafka.py index 5b13c33baf9..3aff7bda60b 100644 --- a/ddtrace/ext/kafka.py +++ b/ddtrace/ext/kafka.py @@ -7,6 +7,7 @@ GROUP_ID = "kafka.group_id" TOMBSTONE = "kafka.tombstone" RECEIVED_MESSAGE = "kafka.received_message" +CLUSTER_ID = "kafka.cluster_id" HOST_LIST = "messaging.kafka.bootstrap.servers" diff --git a/ddtrace/internal/datastreams/kafka.py b/ddtrace/internal/datastreams/kafka.py index 33a9b3bd66b..928a7a29de4 100644 --- a/ddtrace/internal/datastreams/kafka.py +++ b/ddtrace/internal/datastreams/kafka.py @@ -21,6 +21,7 @@ def dsm_kafka_message_produce(instance, args, kwargs, is_serializing, span): from . import data_streams_processor as processor topic = core.get_item("kafka_topic") + cluster_id = core.get_item("kafka_cluster_id") message = get_argument_value(args, kwargs, MESSAGE_ARG_POSITION, "value", optional=True) key = get_argument_value(args, kwargs, KEY_ARG_POSITION, KEY_KWARG_NAME, optional=True) headers = kwargs.get("headers", {}) @@ -30,9 +31,11 @@ def dsm_kafka_message_produce(instance, args, kwargs, is_serializing, span): payload_size += _calculate_byte_size(key) payload_size += _calculate_byte_size(headers) - ctx = processor().set_checkpoint( - ["direction:out", "topic:" + topic, "type:kafka"], payload_size=payload_size, span=span - ) + edge_tags = ["direction:out", "topic:" + topic, "type:kafka"] + if cluster_id: + edge_tags.append("kafka_cluster_id:" + str(cluster_id)) + + ctx = processor().set_checkpoint(edge_tags, payload_size=payload_size, span=span) DsmPathwayCodec.encode(ctx, headers) kwargs["headers"] = headers @@ -67,6 +70,7 @@ def dsm_kafka_message_consume(instance, message, span): headers = {header[0]: header[1] for header in (message.headers() or [])} topic = core.get_item("kafka_topic") + cluster_id = core.get_item("kafka_cluster_id") group = instance._group_id payload_size = 0 @@ -80,8 +84,15 @@ def dsm_kafka_message_consume(instance, message, span): payload_size += _calculate_byte_size(headers) ctx = DsmPathwayCodec.decode(headers, processor()) + + edge_tags = ["direction:in", "group:" + group, "topic:" + topic, "type:kafka"] + if cluster_id: + edge_tags.append("kafka_cluster_id:" + str(cluster_id)) + ctx.set_checkpoint( - ["direction:in", "group:" + group, "topic:" + topic, "type:kafka"], payload_size=payload_size, span=span + edge_tags, + payload_size=payload_size, + span=span, ) if instance._auto_commit: diff --git a/docker-compose.yml b/docker-compose.yml index d1d7ecd094c..6b25f80cf75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,17 +59,22 @@ services: ports: - "127.0.0.1:6379:6379" kafka: - image: bitnami/kafka:3.5.1-debian-11-r3 - ports: - - "127.0.0.1:29092:29092" - environment: - - ALLOW_PLAINTEXT_LISTENER=yes - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:29092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://localhost:29092 - - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT - - KAFKA_CFG_BROKER_ID=1 - - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT - - KAFKA_CFGOFFSETS_TOPIC_REPLICATION_FACTOR=1 + platform: linux/arm64 + image: apache/kafka:3.8.0 + ports: + - "127.0.0.1:29092:29092" + environment: + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:29092 + - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://localhost:29092 + - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT + - KAFKA_NODE_ID=1 + - KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT + - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 + - CLUSTER_ID=5L6g3nShT-eMCtK--X86sw + - KAFKA_PROCESS_ROLES=broker,controller + - KAFKA_CONTROLLER_QUORUM_VOTERS=1@kafka:9093 + - KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER rediscluster: platform: linux/amd64 image: grokzen/redis-cluster:6.2.0 @@ -136,7 +141,7 @@ services: - DD_POOL_TRACE_CHECK_FAILURES=true - DD_DISABLE_ERROR_RESPONSES=true - ENABLED_CHECKS=trace_content_length,trace_stall,meta_tracer_version_header,trace_count_header,trace_peer_service,trace_dd_service - - SNAPSHOT_IGNORED_ATTRS=span_id,trace_id,parent_id,duration,start,metrics.system.pid,metrics.system.process_id,metrics.process_id,meta.runtime-id,meta._dd.p.tid,meta.pathway.hash,metrics._dd.tracer_kr,meta._dd.parent_id + - SNAPSHOT_IGNORED_ATTRS=span_id,trace_id,parent_id,duration,start,metrics.system.pid,metrics.system.process_id,metrics.process_id,meta.runtime-id,meta._dd.p.tid,meta.pathway.hash,metrics._dd.tracer_kr,meta._dd.parent_id,meta.kafka.cluster_id vertica: image: vertica/vertica-ce environment: diff --git a/tests/contrib/kafka/meta.properties b/tests/contrib/kafka/meta.properties new file mode 100644 index 00000000000..b6fb74d86f9 --- /dev/null +++ b/tests/contrib/kafka/meta.properties @@ -0,0 +1,5 @@ +# +#Tue Oct 22 15:40:26 UTC 2024 +node.id=1 +version=1 +cluster.id=5L6g3nShT-eMCtK--X86sw diff --git a/tests/contrib/kafka/test_kafka.py b/tests/contrib/kafka/test_kafka.py index 820f1df1c5b..9bcf4ffc538 100644 --- a/tests/contrib/kafka/test_kafka.py +++ b/tests/contrib/kafka/test_kafka.py @@ -432,18 +432,50 @@ def test_data_streams_kafka(dsm_processor, consumer, producer, kafka_topic): assert len(buckets) == 1 first = list(buckets.values())[0].pathway_stats ctx = DataStreamsCtx(MockedTracer().data_streams_processor, 0, 0, 0) - parent_hash = ctx._compute_hash(sorted(["direction:out", "type:kafka", "topic:{}".format(kafka_topic)]), 0) + parent_hash = ctx._compute_hash( + sorted( + ["direction:out", "kafka_cluster_id:5L6g3nShT-eMCtK--X86sw", "type:kafka", "topic:{}".format(kafka_topic)] + ), + 0, + ) child_hash = ctx._compute_hash( - sorted(["direction:in", "type:kafka", "group:test_group", "topic:{}".format(kafka_topic)]), parent_hash + sorted( + [ + "direction:in", + "kafka_cluster_id:5L6g3nShT-eMCtK--X86sw", + "type:kafka", + "group:test_group", + "topic:{}".format(kafka_topic), + ] + ), + parent_hash, ) assert ( - first[("direction:out,topic:{},type:kafka".format(kafka_topic), parent_hash, 0)].full_pathway_latency.count >= 1 + first[ + ( + "direction:out,kafka_cluster_id:5L6g3nShT-eMCtK--X86sw,topic:{},type:kafka".format(kafka_topic), + parent_hash, + 0, + ) + ].full_pathway_latency.count + >= 1 + ) + assert ( + first[ + ( + "direction:out,kafka_cluster_id:5L6g3nShT-eMCtK--X86sw,topic:{},type:kafka".format(kafka_topic), + parent_hash, + 0, + ) + ].edge_latency.count + >= 1 ) - assert first[("direction:out,topic:{},type:kafka".format(kafka_topic), parent_hash, 0)].edge_latency.count >= 1 assert ( first[ ( - "direction:in,group:test_group,topic:{},type:kafka".format(kafka_topic), + "direction:in,group:test_group,kafka_cluster_id:5L6g3nShT-eMCtK--X86sw,topic:{},type:kafka".format( + kafka_topic + ), child_hash, parent_hash, ) @@ -453,7 +485,9 @@ def test_data_streams_kafka(dsm_processor, consumer, producer, kafka_topic): assert ( first[ ( - "direction:in,group:test_group,topic:{},type:kafka".format(kafka_topic), + "direction:in,group:test_group,kafka_cluster_id:5L6g3nShT-eMCtK--X86sw,topic:{},type:kafka".format( + kafka_topic + ), child_hash, parent_hash, ) diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json index b339e5d1fda..1aaec31a025 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_analytics_with_rate", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json index 9b6ccbba1dd..689eba782a0 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_analytics_without_rate", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json index a995f22cfe0..4302cc6a66c 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json index ec0e59e04c2..64398306ae1 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65dcd1fd00000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65dcd1f900000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_consume_single_message", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json index 21bdb597019..f918fdb6061 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json @@ -48,6 +48,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65df90dd00000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_consume_with_error_False", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json index 4c55c127ba9..b712bb12f4f 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12500000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12000000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_consume_with_multiple_messages", @@ -87,6 +89,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12000000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_consume_with_multiple_messages", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json index 1033a66637d..b49954a04bf 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_offset", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json index 5910c4b2390..896d5024ab9 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_commit_with_only_async_arg", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json index 8b01cc75bbf..10dc4084f33 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_message_False", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json index 94ace7efff5..f3088a8cfd6 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "True", "kafka.topic": "test_message_True", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json index 60678bc1d6b..088cfb7b727 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_None-None", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-mysvc].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-mysvc].json index ccee8ed0c0a..5e9ae025851 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-mysvc].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-mysvc].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_None-mysvc", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json index 1191c838701..a0ad2cf0b8b 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_v0-None", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-mysvc].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-mysvc].json index 01385a0cc72..5350d0353be 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-mysvc].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-mysvc].json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_v0-mysvc", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json index e73c1bdf57d..526df58767d 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json @@ -12,6 +12,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -50,6 +51,7 @@ "_dd.p.tid": "654a694400000000", "_dd.peer.service.source": "messaging.kafka.bootstrap.servers", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_v1-None", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-mysvc].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-mysvc].json index 2fa44ed0109..4da99c0699d 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-mysvc].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-mysvc].json @@ -12,6 +12,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -50,6 +51,7 @@ "_dd.p.tid": "654a694400000000", "_dd.peer.service.source": "messaging.kafka.bootstrap.servers", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_schematized_span_service_and_operation_v1-mysvc", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json index b9a866b5d0d..cf977598cbb 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_service_override_config", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json index 50653113334..f9d9ef25381 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json @@ -13,6 +13,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.group_id": "test_group", "kafka.message_key": "test_key", "kafka.received_message": "True", @@ -51,6 +52,7 @@ "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", + "kafka.cluster_id": "5L6g3nShT-eMCtK--X86sw", "kafka.message_key": "test_key", "kafka.tombstone": "False", "kafka.topic": "test_service_override_env_var", From 3fd669649c3d8547adccdb388396d1acca04fc75 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 24 Oct 2024 19:13:23 +0200 Subject: [PATCH 060/372] chore(ci): remove python 3.9 from appsec_aggregated_leak_testing (#11154) Remove python 3.9 from appsec_aggregated_leak_testing ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/tests/appsec.yml | 2 +- hatch.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/tests/appsec.yml b/.gitlab/tests/appsec.yml index 7b3667a954b..3cb1f360192 100644 --- a/.gitlab/tests/appsec.yml +++ b/.gitlab/tests/appsec.yml @@ -56,7 +56,7 @@ appsec threats fastapi: appsec aggregated leak testing: extends: .test_base_hatch - parallel: 4 + parallel: 3 variables: SUITE_NAME: "appsec_aggregated_leak_testing" retry: 2 diff --git a/hatch.toml b/hatch.toml index 8eef89a6a4e..1c5d853197b 100644 --- a/hatch.toml +++ b/hatch.toml @@ -357,7 +357,7 @@ test = [ ] [[envs.appsec_aggregated_leak_testing.matrix]] -python = ["3.9", "3.10", "3.11", "3.12"] +python = ["3.10", "3.11", "3.12"] From 127e5b72ee45e55e4c6d9b7d07922d47099483fb Mon Sep 17 00:00:00 2001 From: kyle Date: Thu, 24 Oct 2024 16:54:58 -0400 Subject: [PATCH 061/372] fix(llmobs): respect enabled setting when forking (#11026) The code was assuming that the LLMObs service is enabled when forking. If a user had explicitly disabled it via the disable() method or DD_LLMOBS_ENABLED=0 it would be re-enabled in the child process. Potentially closes #10859 --- ddtrace/llmobs/_llmobs.py | 19 ++------ ...lm-obs-fork-disabled-f710f0ccde71c36b.yaml | 4 ++ tests/llmobs/test_llmobs_service.py | 45 ++++++++++++++++++- 3 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/llm-obs-fork-disabled-f710f0ccde71c36b.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 14ae70c1214..1e8caa99861 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -124,26 +124,14 @@ def _child_after_fork(self): self._evaluator_runner = self._evaluator_runner.recreate() self._trace_processor._span_writer = self._llmobs_span_writer self._trace_processor._evaluator_runner = self._evaluator_runner - tracer_filters = self.tracer._filters - if not any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in tracer_filters): - tracer_filters += [self._trace_processor] - self.tracer.configure(settings={"FILTERS": tracer_filters}) - try: - self._llmobs_span_writer.start() - self._llmobs_eval_metric_writer.start() - except ServiceStatusError: - log.debug("Error starting LLMObs writers after fork") - - try: - self._evaluator_runner.start() - except ServiceStatusError: - log.debug("Error starting evaluator runner after fork") + if self.enabled: + self._start_service() def _start_service(self) -> None: tracer_filters = self.tracer._filters if not any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in tracer_filters): tracer_filters += [self._trace_processor] - self.tracer.configure(settings={"FILTERS": tracer_filters}) + self.tracer.configure(settings={"FILTERS": tracer_filters}) try: self._llmobs_span_writer.start() self._llmobs_eval_metric_writer.start() @@ -245,6 +233,7 @@ def enable( if integrations_enabled: cls._patch_integrations() + # override the default _instance with a new tracer cls._instance = cls(tracer=_tracer) cls.enabled = True diff --git a/releasenotes/notes/llm-obs-fork-disabled-f710f0ccde71c36b.yaml b/releasenotes/notes/llm-obs-fork-disabled-f710f0ccde71c36b.yaml new file mode 100644 index 00000000000..30f571bae30 --- /dev/null +++ b/releasenotes/notes/llm-obs-fork-disabled-f710f0ccde71c36b.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + LLM Observability: Resolves errors where the disabled setting was being ignored when forking. diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index c69a0c36829..f174b2c219a 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1574,7 +1574,7 @@ def test_llmobs_fork_recreates_and_restarts_evaluator_runner(mock_ragas_evaluato def test_llmobs_fork_create_span(monkeypatch): """Test that forking a process correctly encodes new spans created in each process.""" - monkeypatch.setenv("_DD_LLMOBS_WRITER_INTERVAL", 5.0) + monkeypatch.setenv("_DD_LLMOBS_WRITER_INTERVAL", "5.0") with mock.patch("ddtrace.internal.writer.HTTPWriter._send_payload"): llmobs_service.enable(_tracer=DummyTracer(), ml_app="test_app") pid = os.fork() @@ -1684,6 +1684,49 @@ def process_trace(self, trace): llmobs_service.disable() +def test_llmobs_fork_disabled(monkeypatch): + """Test that after being disabled the service remains disabled when forking""" + monkeypatch.setenv("DD_LLMOBS_ENABLED", "0") + svc = llmobs_service(tracer=DummyTracer()) + pid = os.fork() + assert not svc.enabled, "both the parent and child should be disabled" + assert svc._llmobs_span_writer.status == ServiceStatus.STOPPED + assert svc._llmobs_eval_metric_writer.status == ServiceStatus.STOPPED + if not pid: + svc.disable() + os._exit(12) + + _, status = os.waitpid(pid, 0) + exit_code = os.WEXITSTATUS(status) + assert exit_code == 12 + svc.disable() + + +def test_llmobs_fork_disabled_then_enabled(monkeypatch): + """Test that after being initially disabled, the service can be enabled in a fork""" + monkeypatch.setenv("DD_LLMOBS_ENABLED", "0") + svc = llmobs_service._instance + pid = os.fork() + assert not svc.enabled, "both the parent and child should be disabled" + assert svc._llmobs_span_writer.status == ServiceStatus.STOPPED + assert svc._llmobs_eval_metric_writer.status == ServiceStatus.STOPPED + if not pid: + # Enable the service in the child + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + monkeypatch.setenv("DD_LLMOBS_ENABLED", "1") + llmobs_service.enable(_tracer=DummyTracer()) + svc = llmobs_service._instance + assert svc._llmobs_span_writer.status == ServiceStatus.RUNNING + assert svc._llmobs_eval_metric_writer.status == ServiceStatus.RUNNING + svc.disable() + os._exit(12) + + _, status = os.waitpid(pid, 0) + exit_code = os.WEXITSTATUS(status) + assert exit_code == 12 + svc.disable() + + def test_annotation_context_modifies_span_tags(LLMObs): with LLMObs.annotation_context(tags={"foo": "bar"}): with LLMObs.agent(name="test_agent") as span: From 0c17d211b9fac9d7855966f70530f19ed17e27a3 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 25 Oct 2024 09:42:56 +0200 Subject: [PATCH 062/372] chore(ci): fix django snapshot timeouts (#11138) This PR improves and fixes issues in the Django tests with the following changes: - Share the PATH and PYTHONPATH with the subprocess: When a script runs in a subprocess, there are times in the CI or locally when it's assigned a different path than expected. Even worse, we've seen scripts that worked for months suddenly stop working because of this. With this function, we always set the path to ensure consistent results both locally and across different CI environments - Replace the `ddtrace-run` command with `python -m ddtrace.commands.ddtrace_run` to ensure that the local library is executed, rather than a potentially installed version - Displays the stdout and stderr if there was an issue in the subprocess ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Gabriele N. Tornetta --- tests/appsec/appsec_utils.py | 19 ++---- tests/contrib/django/test_django_snapshots.py | 64 +++++++++++++------ tests/internal/test_module.py | 18 ++---- tests/telemetry/test_telemetry_metrics_e2e.py | 16 ++--- tests/utils.py | 26 ++++++-- tests/webclient.py | 2 +- 6 files changed, 78 insertions(+), 67 deletions(-) diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index 50d63f133f3..1ec13f29a76 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -1,5 +1,6 @@ from contextlib import contextmanager import os +from pathlib import Path import signal import subprocess import sys @@ -10,23 +11,11 @@ from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.utils.retry import RetryError from ddtrace.vendor import psutil +from tests.utils import _build_env from tests.webclient import Client -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - - -def _build_env(env=None): - environ = dict(PATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR), PYTHONPATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR)) - if os.environ.get("PATH"): - environ["PATH"] = "%s:%s" % (os.environ.get("PATH"), environ["PATH"]) - if os.environ.get("PYTHONPATH"): - environ["PYTHONPATH"] = "%s:%s" % (os.environ.get("PYTHONPATH"), environ["PYTHONPATH"]) - if env: - for k, v in env.items(): - environ[k] = v - return environ +FILE_PATH = Path(__file__).resolve().parent @contextmanager @@ -93,7 +82,7 @@ def appsec_application_server( port=8000, assert_debug=False, ): - env = _build_env(env) + env = _build_env(env, file_path=FILE_PATH) env["DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS"] = "0.5" env["DD_REMOTE_CONFIGURATION_ENABLED"] = remote_configuration_enabled if token: diff --git a/tests/contrib/django/test_django_snapshots.py b/tests/contrib/django/test_django_snapshots.py index fe3e7af3377..feaead253d8 100644 --- a/tests/contrib/django/test_django_snapshots.py +++ b/tests/contrib/django/test_django_snapshots.py @@ -1,11 +1,13 @@ from contextlib import contextmanager import os +from pathlib import Path import subprocess import sys import django import pytest +from tests.utils import _build_env from tests.utils import flaky from tests.utils import package_installed from tests.utils import snapshot @@ -17,6 +19,8 @@ # FIXME: db.name behaves unreliably for some of these tests SNAPSHOT_IGNORES = ["metrics._sampling_priority_v1", "meta.db.name"] +FILE_PATH = Path(__file__).resolve().parent + @contextmanager def daphne_client(django_asgi, additional_env=None): @@ -29,7 +33,7 @@ def daphne_client(django_asgi, additional_env=None): # Make sure to copy the environment as we need the PYTHONPATH and _DD_TRACE_WRITER_ADDITIONAL_HEADERS (for the test # token) propagated to the new process. - env = os.environ.copy() + env = _build_env(os.environ.copy(), file_path=FILE_PATH) env.update(additional_env or {}) assert "_DD_TRACE_WRITER_ADDITIONAL_HEADERS" in env, "Client fixture needs test token in headers" env.update( @@ -37,29 +41,47 @@ def daphne_client(django_asgi, additional_env=None): "DJANGO_SETTINGS_MODULE": "tests.contrib.django.django_app.settings", } ) - # ddtrace-run uses execl which replaces the process but the webserver process itself might spawn new processes. # Right now it doesn't but it's possible that it might in the future (ex. uwsgi). - cmd = ["ddtrace-run", "daphne", "-p", str(SERVER_PORT), "tests.contrib.django.asgi:%s" % django_asgi] - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - env=env, - ) - + cmd = [ + "python", + "-m", + "ddtrace.commands.ddtrace_run", + "daphne", + "-p", + str(SERVER_PORT), + "tests.contrib.django.asgi:%s" % django_asgi, + ] + subprocess_kwargs = { + "env": env, + "start_new_session": True, + "stdout": subprocess.PIPE, + "stderr": subprocess.PIPE, + "close_fds": True, + } + + server_process = subprocess.Popen(cmd, **subprocess_kwargs) client = Client("http://localhost:%d" % SERVER_PORT) # Wait for the server to start up - client.wait() + try: + print("Waiting for server to start") + client.wait(max_tries=120, delay=0.2, initial_wait=2.0) + print("Server started") + except Exception: + raise AssertionError( + "Server failed to start, see stdout and stderr logs" + "\n=== Captured STDOUT ===\n%s=== End of captured STDOUT ===" + "\n=== Captured STDERR ===\n%s=== End of captured STDERR ===" + % (server_process.stdout.read(), server_process.stderr.read()) + ) try: - yield (client, proc) + yield (client, server_process) finally: resp = client.get_ignored("/shutdown-tracer") assert resp.status_code == 200 - proc.terminate() + server_process.terminate() @flaky(1735812000) @@ -70,7 +92,7 @@ def test_urlpatterns_include(client): When a view is specified using `django.urls.include` The view is traced """ - assert client.get("/include/test/").status_code == 200 + assert client.get("/include/test/", timeout=5).status_code == 200 @snapshot( @@ -235,7 +257,7 @@ def test_psycopg3_query_default(client, snapshot_context, psycopg3_patched): @pytest.mark.parametrize("django_asgi", ["application", "channels_application"]) def test_asgi_200(django_asgi): with daphne_client(django_asgi) as (client, _): - resp = client.get("/") + resp = client.get("/", timeout=10) assert resp.status_code == 200 assert resp.content == b"Hello, test app." @@ -256,7 +278,7 @@ def test_asgi_200_simple_app(): @snapshot(ignores=SNAPSHOT_IGNORES + ["meta.http.useragent"]) def test_asgi_200_traced_simple_app(): with daphne_client("channels_application") as (client, _): - resp = client.get("/traced-simple-asgi-app/") + resp = client.get("/traced-simple-asgi-app/", timeout=10) assert resp.status_code == 200 assert resp.content == b"Hello World. It's me simple asgi app" @@ -284,7 +306,7 @@ def test_asgi_500(): def test_templates_enabled(): """Default behavior to compare with disabled variant""" with daphne_client("application") as (client, _): - resp = client.get("/template-view/") + resp = client.get("/template-view/", timeout=10) assert resp.status_code == 200 assert resp.content == b"some content\n" @@ -299,7 +321,7 @@ def test_templates_enabled(): def test_templates_disabled(): """Template instrumentation disabled""" with daphne_client("application", additional_env={"DD_DJANGO_INSTRUMENT_TEMPLATES": "false"}) as (client, _): - resp = client.get("/template-view/") + resp = client.get("/template-view/", timeout=10) assert resp.status_code == 200 assert resp.content == b"some content\n" @@ -327,7 +349,7 @@ def test_djangoq_dd_trace_methods(dd_trace_methods, error_expected): if error_expected is True: pytest.xfail() with daphne_client("application", additional_env={"DD_TRACE_METHODS": dd_trace_methods}) as (client, proc): - assert client.get("simple/").status_code == 200 + assert client.get("simple/", timeout=10).status_code == 200 - _, stderr = proc.communicate(timeout=5) + _, stderr = proc.communicate(timeout=10) assert (b"error configuring Datadog tracing" in stderr) == error_expected diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index a78d63cc4ff..68261331f7b 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -10,19 +10,11 @@ from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.module import origin import tests.test_module +from tests.utils import DDTRACE_PATH +from tests.utils import _build_env -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - - -def _build_env(): - environ = dict(PATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR), PYTHONPATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR)) - if os.environ.get("PATH"): - environ["PATH"] = "%s:%s" % (os.environ.get("PATH"), environ["PATH"]) - if os.environ.get("PYTHONPATH"): - environ["PYTHONPATH"] = "%s:%s" % (os.environ.get("PYTHONPATH"), environ["PYTHONPATH"]) - return environ +FILE_PATH = Path(__file__).resolve().parent @pytest.fixture(autouse=True, scope="module") @@ -303,7 +295,7 @@ def after_import(self, module): @pytest.mark.subprocess( out="post_run_module_hook OK\n", - env=_build_env(), + env=_build_env(file_path=FILE_PATH), run_module=True, ) def test_post_run_module_hook(): @@ -554,7 +546,7 @@ def __getattr__(name): raise AttributeError("%s has no attribute %s", __name__, name) """ - contrib_dir = Path(ROOT_PROJECT_DIR) / "ddtrace" / "contrib" + contrib_dir = Path(DDTRACE_PATH) / "ddtrace" / "contrib" missing_deprecations = set() for directory, _, file_names in os.walk(contrib_dir): diff --git a/tests/telemetry/test_telemetry_metrics_e2e.py b/tests/telemetry/test_telemetry_metrics_e2e.py index 77b10c508ef..30033458cef 100644 --- a/tests/telemetry/test_telemetry_metrics_e2e.py +++ b/tests/telemetry/test_telemetry_metrics_e2e.py @@ -1,30 +1,22 @@ from contextlib import contextmanager import json import os +from pathlib import Path import subprocess import sys from ddtrace.internal.utils.retry import RetryError +from tests.utils import _build_env from tests.webclient import Client -ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - - -def _build_env(): - environ = dict(PATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR), PYTHONPATH="%s:%s" % (ROOT_PROJECT_DIR, ROOT_DIR)) - if os.environ.get("PATH"): - environ["PATH"] = "%s:%s" % (os.environ.get("PATH"), environ["PATH"]) - if os.environ.get("PYTHONPATH"): - environ["PYTHONPATH"] = "%s:%s" % (os.environ.get("PYTHONPATH"), environ["PYTHONPATH"]) - return environ +FILE_PATH = Path(__file__).resolve().parent @contextmanager def gunicorn_server(telemetry_metrics_enabled="true", token=None): cmd = ["ddtrace-run", "gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "tests.telemetry.app:app"] - env = _build_env() + env = _build_env(file_path=FILE_PATH) env["_DD_TRACE_WRITER_ADDITIONAL_HEADERS"] = "X-Datadog-Test-Session-Token:{}".format(token) env["DD_TRACE_AGENT_URL"] = os.environ.get("DD_TRACE_AGENT_URL", "") env["DD_TRACE_DEBUG"] = "true" diff --git a/tests/utils.py b/tests/utils.py index 1fb8d8fa96a..7f07d6c77d6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,6 +6,7 @@ import inspect import json import os +from pathlib import Path import subprocess import sys import time @@ -47,6 +48,8 @@ import importlib_metadata NO_CHILDREN = object() +DDTRACE_PATH = Path(__file__).resolve().parents[1] +FILE_PATH = Path(__file__).resolve().parent def assert_is_measured(span): @@ -1117,14 +1120,10 @@ def snapshot_context( # "received unmatched traces" can sometimes happen else: pytest.xfail(result) - except Exception as e: - # Even though it's unlikely any traces have been sent, make the - # final request to the test agent so that the test case is finished. + finally: conn = httplib.HTTPConnection(parsed.hostname, parsed.port) conn.request("GET", "/test/session/snapshot?ignores=%s&test_session_token=%s" % (",".join(ignores), token)) conn.getresponse() - pytest.fail("Unexpected test failure during snapshot test: %s" % str(e), pytrace=True) - finally: conn.close() @@ -1358,3 +1357,20 @@ def decorator(function_or_class): return _get_skipped_item(function_or_class, full_reason) return decorator + + +def _build_env(env=None, file_path=FILE_PATH): + """When a script runs in a subprocess, there are times in the CI or locally when it's assigned a different + path than expected. Even worse, we've seen scripts that worked for months suddenly stop working because of this. + With this function, we always set the path to ensure consistent results both locally and across different + CI environments + """ + environ = dict(PATH="%s:%s" % (DDTRACE_PATH, file_path), PYTHONPATH="%s:%s" % (DDTRACE_PATH, file_path)) + if os.environ.get("PATH"): + environ["PATH"] = "%s:%s" % (os.environ.get("PATH"), environ["PATH"]) + if os.environ.get("PYTHONPATH"): + environ["PYTHONPATH"] = "%s:%s" % (os.environ.get("PYTHONPATH"), environ["PYTHONPATH"]) + if env: + for k, v in env.items(): + environ[k] = v + return environ diff --git a/tests/webclient.py b/tests/webclient.py index 100b7b45f33..7254a0896dd 100644 --- a/tests/webclient.py +++ b/tests/webclient.py @@ -49,7 +49,7 @@ def wait(self, path="/", max_tries=100, delay=0.1, initial_wait=0): @retry(after=[delay] * (max_tries - 1), initial_wait=initial_wait) def ping(): - r = self.get_ignored(path, timeout=1) + r = self.get_ignored(path, timeout=10) assert r.status_code == 200 ping() From 574fcc055326476319849a64d15edda6667523cf Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 25 Oct 2024 06:09:59 -0400 Subject: [PATCH 063/372] fix(profiling): handle `Span`s with `None` span type for stack v2 endpoint profiling (#11164) Fixes #11141 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../profiling/stack_v2/src/stack_v2.cpp | 10 +++- ...point-span-type-none-48a1196296be3742.yaml | 5 ++ tests/profiling_v2/collector/test_stack.py | 55 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/profiling-stack-v2-endpoint-span-type-none-48a1196296be3742.yaml diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp index ee7338f0cbd..7b67a19c4c3 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp @@ -91,7 +91,7 @@ _stack_v2_link_span(PyObject* self, PyObject* args, PyObject* kwargs) uint64_t thread_id; uint64_t span_id; uint64_t local_root_span_id; - const char* span_type; + const char* span_type = nullptr; PyThreadState* state = PyThreadState_Get(); @@ -104,10 +104,16 @@ _stack_v2_link_span(PyObject* self, PyObject* args, PyObject* kwargs) static const char* const_kwlist[] = { "span_id", "local_root_span_id", "span_type", NULL }; static char** kwlist = const_cast(const_kwlist); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "KKs", kwlist, &span_id, &local_root_span_id, &span_type)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "KKz", kwlist, &span_id, &local_root_span_id, &span_type)) { return NULL; } + // From Python, span_type is a string or None, and when given None, it is passed as a nullptr. + static const std::string empty_string = ""; + if (span_type == nullptr) { + span_type = empty_string.c_str(); + } + ThreadSpanLinks::get_instance().link_span(thread_id, span_id, local_root_span_id, std::string(span_type)); Py_RETURN_NONE; diff --git a/releasenotes/notes/profiling-stack-v2-endpoint-span-type-none-48a1196296be3742.yaml b/releasenotes/notes/profiling-stack-v2-endpoint-span-type-none-48a1196296be3742.yaml new file mode 100644 index 00000000000..1e4fea09afe --- /dev/null +++ b/releasenotes/notes/profiling-stack-v2-endpoint-span-type-none-48a1196296be3742.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: resolves an issue where endpoint profiling for stack v2 throws + ``TypeError`` exception when it is given a ``Span`` with ``None`` span_type. diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 4a0514bcc3c..c09fc381801 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -221,3 +221,58 @@ def test_push_non_web_span(): # trace_endpoint is not set for non-web spans ), ) + + +@pytest.mark.subprocess(env=dict(DD_PROFILING_STACK_V2_ENABLED="true")) +def test_push_span_none_span_type(): + # Test for https://github.com/DataDog/dd-trace-py/issues/11141 + import os + import time + import uuid + + from ddtrace import tracer + from ddtrace.internal.datadog.profiling import ddup + from ddtrace.profiling.collector import stack + from tests.profiling.collector import pprof_utils + + test_name = "test_push_span_none_span_type" + pprof_prefix = "/tmp/" + test_name + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + tracer._endpoint_call_counter_span_processor.enable() + + resource = str(uuid.uuid4()) + + with stack.StackCollector( + None, + tracer=tracer, + endpoint_collection_enabled=True, + ignore_profiler=True, # this is not necessary, but it's here to trim samples + ): + # Explicitly set None span_type as the default could change in the + # future. + with tracer.trace("foobar", resource=resource, span_type=None) as span: + span_id = span.span_id + local_root_span_id = span._local_root.span_id + for _ in range(5): + time.sleep(0.01) + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "span id") + assert len(samples) > 0 + for sample in samples: + pprof_utils.assert_stack_event( + profile, + sample, + expected_event=pprof_utils.StackEvent( + span_id=span_id, + local_root_span_id=local_root_span_id, + # span_type is None + # trace_endpoint is not set for non-web spans + ), + ) From f259c19a80b9d342da4623538bd7cf28689846a4 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 25 Oct 2024 09:33:11 -0400 Subject: [PATCH 064/372] refactor(profiling): remove pytest.mark.subprocess from v2/test_stack.py (#11139) Need to be merged after #11164 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../stack_v2/include/thread_span_links.hpp | 4 +- .../profiling/stack_v2/src/stack_v2.cpp | 5 + tests/profiling/collector/pprof_utils.py | 29 ++-- tests/profiling_v2/collector/test_stack.py | 141 +++++------------- 4 files changed, 59 insertions(+), 120 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp index 061cb123c03..5f2f69e39ab 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp @@ -36,8 +36,8 @@ class ThreadSpanLinks ThreadSpanLinks& operator=(ThreadSpanLinks const&) = delete; void link_span(uint64_t thread_id, uint64_t span_id, uint64_t local_root_span_id, std::string span_type); - const Span* get_active_span_from_thread_id(uint64_t thread_id); + void reset(); static void postfork_child(); @@ -48,8 +48,6 @@ class ThreadSpanLinks // Private Constructor/Destructor ThreadSpanLinks() = default; ~ThreadSpanLinks() = default; - - void reset(); }; } diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp index 7b67a19c4c3..b7402ee7a2d 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp @@ -34,6 +34,11 @@ stack_v2_stop(PyObject* self, PyObject* args) (void)self; (void)args; Sampler::get().stop(); + // Explicitly clear ThreadSpanLinks. The memory should be cleared up + // when the program exits as ThreadSpanLinks is a static singleton instance. + // However, this was necessary to make sure that the state is not shared + // across tests, as the tests are run in the same process. + ThreadSpanLinks::get_instance().reset(); Py_RETURN_NONE; } diff --git a/tests/profiling/collector/pprof_utils.py b/tests/profiling/collector/pprof_utils.py index a9a2425dca6..7d2c980745e 100644 --- a/tests/profiling/collector/pprof_utils.py +++ b/tests/profiling/collector/pprof_utils.py @@ -1,6 +1,7 @@ import ctypes from enum import Enum import glob +import os import re import typing @@ -273,20 +274,24 @@ def assert_lock_event(profile, sample: pprof_pb2.Sample, expected_event: LockEve # helper function to check whether the expected stack event is present in the samples -def has_sample_with_locations( - profile, samples: typing.List[pprof_pb2.Sample], expected_locations: typing.List[StackLocation] -) -> bool: - for sample in samples: - for location_id, expected_location in zip(sample.location_id, expected_locations): +def has_sample_with_locations(profile, expected_locations: typing.List[StackLocation]) -> bool: + for sample_idx, sample in enumerate(profile.sample): + # in a sample there can be multiple locations, we need to check + # whether there's a consecutive subsequence of locations that match + # the expected locations + expected_location_idx = 0 + for location_id in sample.location_id: location = get_location_with_id(profile, location_id) function = get_function_with_id(profile, location.line[0].function_id) - if profile.string_table[function.name] != expected_location.function_name: - continue - if not profile.string_table[function.filename].endswith(expected_location.filename): - continue - - return True - + function_name = profile.string_table[function.name] + filename = os.path.basename(profile.string_table[function.filename]) + if ( + function_name.endswith(expected_locations[expected_location_idx].function_name) + and filename == expected_locations[expected_location_idx].filename + ): + expected_location_idx += 1 + if expected_location_idx == len(expected_locations): + return True return False diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index c09fc381801..571db4f8811 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -1,17 +1,24 @@ +import os +import sys +import time +import uuid + import pytest +from ddtrace import ext +from ddtrace import tracer +from ddtrace.internal.datadog.profiling import ddup +from ddtrace.profiling.collector import stack +from tests.profiling.collector import pprof_utils -@pytest.mark.subprocess(env=dict(DD_PROFILING_STACK_V2_ENABLED="true")) -def test_stack_v2_locations(): - import os - import time - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_stack_v2_locations(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_locations" - pprof_prefix = "/tmp/" + test_name + pprof_prefix = str(tmp_path / test_name) output_filename = pprof_prefix + "." + str(os.getpid()) assert ddup.is_available @@ -27,7 +34,7 @@ def bar(): def foo(): bar() - with stack.StackCollector(None): + with stack.StackCollector(None, _stack_collector_v2_enabled=stack_v2_enabled): for _ in range(5): foo() ddup.upload() @@ -52,91 +59,25 @@ def foo(): ] assert pprof_utils.has_sample_with_locations( - profile, samples, expected_locations + profile, expected_locations ), "Sample with expected locations not found" -# Tests here are marked as subprocess as they are flaky when not marked as such, -# similar to test_user_threads_have_native_id in test_threading.py. For some -# reason, when the Span is created, it's not linked to the MainThread, and the -# profiler can't find the corresponding Span for the MainThread. -@pytest.mark.subprocess() -def test_push_span(): - import os - import time - import uuid - - from ddtrace import ext - from ddtrace import tracer - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_push_span(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_push_span" - pprof_prefix = "/tmp/" + test_name + pprof_prefix = str(tmp_path / test_name) output_filename = pprof_prefix + "." + str(os.getpid()) - assert ddup.is_available - ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) - ddup.start() - tracer._endpoint_call_counter_span_processor.enable() - resource = str(uuid.uuid4()) - span_type = ext.SpanTypes.WEB - - with stack.StackCollector( - None, - tracer=tracer, - endpoint_collection_enabled=True, - ignore_profiler=True, # this is not necessary, but it's here to trim samples - ): - with tracer.trace("foobar", resource=resource, span_type=span_type) as span: - span_id = span.span_id - local_root_span_id = span._local_root.span_id - for _ in range(5): - time.sleep(0.01) - ddup.upload() - - profile = pprof_utils.parse_profile(output_filename) - samples = pprof_utils.get_samples_with_label_key(profile, "span id") - assert len(samples) > 0 - for sample in samples: - pprof_utils.assert_stack_event( - profile, - sample, - expected_event=pprof_utils.StackEvent( - span_id=span_id, - local_root_span_id=local_root_span_id, - trace_type=span_type, - trace_endpoint=resource, - ), - ) - - -# pytest.mark.subprocess doesn't support parametrize, so duplicate code here -@pytest.mark.subprocess(env=dict(DD_PROFILING_STACK_V2_ENABLED="true")) -def test_push_span_v2(): - import os - import time - import uuid - - from ddtrace import ext - from ddtrace import tracer - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils - - test_name = "test_push_span_v2" - pprof_prefix = "/tmp/" + test_name - output_filename = pprof_prefix + "." + str(os.getpid()) - assert ddup.is_available ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) ddup.start() - tracer._endpoint_call_counter_span_processor.enable() - resource = str(uuid.uuid4()) span_type = ext.SpanTypes.WEB @@ -145,6 +86,7 @@ def test_push_span_v2(): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples + _stack_collector_v2_enabled=stack_v2_enabled, ): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id @@ -169,28 +111,21 @@ def test_push_span_v2(): ) -@pytest.mark.subprocess() -def test_push_non_web_span(): - import os - import time - import uuid +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_push_non_web_span(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") - from ddtrace import ext - from ddtrace import tracer - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils + tracer._endpoint_call_counter_span_processor.enable() test_name = "test_push_non_web_span" - pprof_prefix = "/tmp/" + test_name + pprof_prefix = str(tmp_path / test_name) output_filename = pprof_prefix + "." + str(os.getpid()) assert ddup.is_available ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) ddup.start() - tracer._endpoint_call_counter_span_processor.enable() - resource = str(uuid.uuid4()) span_type = ext.SpanTypes.SQL @@ -199,6 +134,7 @@ def test_push_non_web_span(): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples + _stack_collector_v2_enabled=stack_v2_enabled, ): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id @@ -223,20 +159,14 @@ def test_push_non_web_span(): ) -@pytest.mark.subprocess(env=dict(DD_PROFILING_STACK_V2_ENABLED="true")) -def test_push_span_none_span_type(): +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_push_span_none_span_type(stack_v2_enabled, tmp_path): # Test for https://github.com/DataDog/dd-trace-py/issues/11141 - import os - import time - import uuid - - from ddtrace import tracer - from ddtrace.internal.datadog.profiling import ddup - from ddtrace.profiling.collector import stack - from tests.profiling.collector import pprof_utils + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_push_span_none_span_type" - pprof_prefix = "/tmp/" + test_name + pprof_prefix = str(tmp_path / test_name) output_filename = pprof_prefix + "." + str(os.getpid()) assert ddup.is_available @@ -252,6 +182,7 @@ def test_push_span_none_span_type(): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples + _stack_collector_v2_enabled=stack_v2_enabled, ): # Explicitly set None span_type as the default could change in the # future. From d3485f2d9c91e5f8337d8524648eb967d2b7031e Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 25 Oct 2024 16:10:49 +0200 Subject: [PATCH 065/372] chore(ci): asm config reset in tests (#11171) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/asm.py | 5 +++- .../appsec/appsec/test_remoteconfiguration.py | 16 +++++----- tests/appsec/iast/aspects/test_add_aspect.py | 30 +++++++++---------- tests/utils.py | 6 +++- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 487045d27ff..f877c7ffc10 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -214,7 +214,7 @@ class ASMConfig(Env): def __init__(self): super().__init__() # Is one click available? - self._asm_can_be_enabled = APPSEC_ENV not in os.environ and tracer_config._remote_config_enabled + self._eval_asm_can_be_enabled() # Only for deprecation phase if self._automatic_login_events_mode and APPSEC.AUTO_USER_INSTRUMENTATION_MODE not in os.environ: self._auto_user_instrumentation_local_mode = self._automatic_login_events_mode @@ -228,6 +228,9 @@ def reset(self): """For testing puposes, reset the configuration to its default values given current environment variables.""" self.__init__() + def _eval_asm_can_be_enabled(self): + self._asm_can_be_enabled = APPSEC_ENV not in os.environ and tracer_config._remote_config_enabled + @property def _api_security_feature_active(self) -> bool: return self._asm_libddwaf_available and self._asm_enabled and self._api_security_enabled diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index ade6543f8c9..c2aff2099e2 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -99,19 +99,19 @@ def test_rc_activation_states_on(tracer, appsec_enabled, rc_value, remote_config ], ) def test_rc_activation_states_off(tracer, appsec_enabled, rc_value, remote_config_worker): - with override_env({APPSEC.ENV: appsec_enabled}), override_global_config(dict(_asm_enabled=True)): + with override_env({APPSEC.ENV: appsec_enabled}): if appsec_enabled == "": del os.environ[APPSEC.ENV] - else: + with override_global_config(dict(_asm_enabled=True)): tracer.configure(appsec_enabled=asbool(appsec_enabled)) - rc_config = {"config": {"asm": {"enabled": True}}} - if rc_value is False: - rc_config = {} + rc_config = {"config": {"asm": {"enabled": True}}} + if rc_value is False: + rc_config = {} - _appsec_callback(rc_config, tracer) - result = _set_and_get_appsec_tags(tracer) - assert result is None + _appsec_callback(rc_config, tracer) + result = _set_and_get_appsec_tags(tracer) + assert result is None @pytest.mark.parametrize( diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index 8e75ed3d1b9..db8f9b212c8 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -269,24 +269,24 @@ def test_taint_object_error_with_no_context(log_level, iast_debug, caplog): source_origin=OriginType.PARAMETER, ) - ranges_result = get_tainted_ranges(result) - assert len(ranges_result) == 0 + ranges_result = get_tainted_ranges(result) + assert len(ranges_result) == 0 - assert not any(record.message.startswith("Tainting object error") for record in caplog.records), [ - record.message for record in caplog.records - ] - assert not any("[IAST] Tainted Map" in record.message for record in caplog.records) + assert not any(record.message.startswith("Tainting object error") for record in caplog.records), [ + record.message for record in caplog.records + ] + assert not any("[IAST] Tainted Map" in record.message for record in caplog.records) - _start_iast_context_and_oce() - result = taint_pyobject( - pyobject=string_to_taint, - source_name="test_add_aspect_tainting_left_hand", - source_value=string_to_taint, - source_origin=OriginType.PARAMETER, - ) + _start_iast_context_and_oce() + result = taint_pyobject( + pyobject=string_to_taint, + source_name="test_add_aspect_tainting_left_hand", + source_value=string_to_taint, + source_origin=OriginType.PARAMETER, + ) - ranges_result = get_tainted_ranges(result) - assert len(ranges_result) == 1 + ranges_result = get_tainted_ranges(result) + assert len(ranges_result) == 1 @pytest.mark.skip_iast_check_logs diff --git a/tests/utils.py b/tests/utils.py index 7f07d6c77d6..fcf494075e4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -175,18 +175,22 @@ def override_global_config(values): if key in global_config_keys: setattr(ddtrace.config, key, value) # rebuild asm config from env vars and global config - ddtrace.settings.asm.config.reset() for key, value in values.items(): if key in asm_config_keys: setattr(ddtrace.settings.asm.config, key, value) + # If ddtrace.settings.asm.config has changed, check _asm_can_be_enabled again + ddtrace.settings.asm.config._eval_asm_can_be_enabled() try: yield finally: # Reset all to their original values for key, value in originals.items(): setattr(ddtrace.config, key, value) + + ddtrace.settings.asm.config.reset() for key, value in asm_originals.items(): setattr(ddtrace.settings.asm.config, key, value) + ddtrace.config._reset() ddtrace.config._subscriptions = subscriptions From 73417b9ce11034c92eb897dd6c4d31d004955963 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:56:57 -0400 Subject: [PATCH 066/372] chore: update changelog for version 2.11.8, 2.12.4, 2.13.2, and 2.14.4 (#11071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [x] update changelog for version 2.11.8, 2.12.4, 2.13.2, and 2.14.4 --------- Co-authored-by: Vítor De Araújo --- CHANGELOG.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68b037bb2c..f225cbf53bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,57 @@ Changelogs for versions not listed here can be found at https://github.com/DataD --- +## 2.14.4 + + +### Bug Fixes +- Code Security + - Ensures IAST propagation does not raise side effects related to `re.finditer`. +- LLM Observability + - botocore: Fixes bedrock model and model provider interpretation from `modelId` when using cross-region inference. +- Profiling + - Fixes an issue where stack v2 couldn't be enabled as pthread was not properly linked on some debian based images for aarch64 architecture. +- Tracing + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + +--- + +## 2.13.2 + + +### Bug Fixes +- Code Security + - Ensures IAST propagation does not raise side effects related to `re.finditer`. +- LLM Observability + - botocore: Fixes bedrock model and model provider interpretation from `modelId` when using cross-region inference. +- Profiling + - Fixes an issue where stack v2 couldn't be enabled as pthread was not properly linked on some debian based images for aarch64 architecture. +- Tracing + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + +--- + +## 2.12.4 + + +### Bug Fixes +- Profiling + - Fixes an issue where stack v2 couldn't be enabled as pthread was not properly linked on some debian based images for aarch64 architecture. +- Tracing + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + +--- + +## 2.11.8 + + +### Bug Fixes + +- Tracing + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + +--- + ## 2.11.7 ### Bug Fixes From a5d52c17a14d9f5a06117c4211f1cf8275f35e58 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 25 Oct 2024 21:01:14 +0200 Subject: [PATCH 067/372] chore(iast): copy_ranges_to_string errors (#11170) This fix is not enough we add extra validations https://github.com/DataDog/dd-trace-py/pull/11107/files#diff-187a73402e1c19057794a003245a9ee6087a6ee4a62ca2739777529eda47c6d8R267 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitignore | 3 ++ .../appsec/_iast/_taint_tracking/__init__.py | 42 ++++++++++++------- tests/appsec/iast/aspects/test_re_aspects.py | 28 ++++++++++++- tests/appsec/iast_packages/test_packages.py | 33 +++++++-------- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index b2f71518924..92f055b78ce 100644 --- a/.gitignore +++ b/.gitignore @@ -157,6 +157,9 @@ ddtrace/appsec/_iast/_taint_tracking/tests/native_tests* ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/Makefile ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeFiles/* ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/cmake_install.cmake +# Those folders are required for tests/appsec/iast_packages/test_packages.py +template_venv/ +cloned_venvs/ # CircleCI generated config .circleci/config.gen.yml diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 02ee7a00e1c..7c6d12cecf2 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -260,24 +260,36 @@ def trace_calls_and_returns(frame, event, arg): threading.settrace(trace_calls_and_returns) -def copy_ranges_to_string(s: str, ranges: Sequence[TaintRange]) -> str: - if not isinstance(s, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return s +def copy_ranges_to_string(pyobject: str, ranges: Sequence[TaintRange]) -> str: + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return pyobject + for r in ranges: - if r.source.value and s in r.source.value: - s = _taint_pyobject_base( - pyobject=s, source_name=r.source.name, source_value=r.source.value, source_origin=r.source.origin + _is_string_in_source_value = False + if r.source.value: + if isinstance(pyobject, (bytes, bytearray)): + pyobject_str = str(pyobject, encoding="utf8", errors="ignore") + else: + pyobject_str = pyobject + _is_string_in_source_value = pyobject_str in r.source.value + + if _is_string_in_source_value: + pyobject = _taint_pyobject_base( + pyobject=pyobject, + source_name=r.source.name, + source_value=r.source.value, + source_origin=r.source.origin, ) break - else: - # no total match found, maybe partial match, just take the first one - s = _taint_pyobject_base( - pyobject=s, - source_name=ranges[0].source.name, - source_value=ranges[0].source.value, - source_origin=ranges[0].source.origin, - ) - return s + else: + # no total match found, maybe partial match, just take the first one + pyobject = _taint_pyobject_base( + pyobject=pyobject, + source_name=ranges[0].source.name, + source_value=ranges[0].source.value, + source_origin=ranges[0].source.origin, + ) + return pyobject # Given a list of ranges, try to match them with the iterable and return a new iterable with a new range applied that diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index c8dd93b97dc..83f0f470854 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -449,6 +449,32 @@ def test_re_search_aspect_tainted_string_re_module(): assert not is_pyobject_tainted(res_str) +def test_re_search_aspect_tainted_bytes_re_module(): + tainted_isaac_newton = taint_pyobject( + pyobject=b"Isaac Newton, physicist", + source_name="test_re_search_group_aspect_tainted_string", + source_value="Isaac Newton, physicist", + source_origin=OriginType.PARAMETER, + ) + + re_search = re_search_aspect(None, 1, re, rb"(\w+) (\w+)", tainted_isaac_newton) + result = re_groups_aspect(None, 1, re_search) + assert result == (b"Isaac", b"Newton") + for res_str in result: + if len(res_str): + assert get_tainted_ranges(res_str) == [ + TaintRange( + 0, + len(res_str), + Source( + "test_re_search_group_aspect_tainted_string", "Isaac Newton, physicist", OriginType.PARAMETER + ), + ), + ] + else: + assert not is_pyobject_tainted(res_str) + + def test_re_search_aspect_not_tainted_re_module(): not_tainted_isaac_newton = "Isaac Newton, physicist" @@ -566,7 +592,7 @@ def test_re_finditer_aspect_tainted_bytes(): tainted_multipart = taint_pyobject( pyobject=b' name="files"; filename="test.txt"\r\nContent-Type: text/plain', source_name="test_re_finditer_aspect_tainted_string", - source_value=b"/foo/bar/baaz.jpeg", + source_value="/foo/bar/baaz.jpeg", source_origin=OriginType.PARAMETER, ) SPECIAL_CHARS = re.escape(b'()<>@,;:\\"/[]?={} \t') diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index fae40bf89e5..77b7f6f6b4e 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -1,5 +1,6 @@ import json import os +from pathlib import Path import shutil import subprocess import sys @@ -10,6 +11,7 @@ from ddtrace.appsec._constants import IAST from tests.appsec.appsec_utils import flask_server +from tests.utils import DDTRACE_PATH from tests.utils import override_env @@ -24,6 +26,17 @@ os.environ[IAST.PATCH_MODULES] = IAST.SEP_MODULES.join(["moto", "moto[all]", "moto[ec2]", "moto[s3]"]) +FILE_PATH = Path(__file__).resolve().parent +_INSIDE_ENV_RUNNER_PATH = os.path.join(FILE_PATH, "inside_env_runner.py") +# Use this function if you want to test one or a filter number of package for debug proposes +# SKIP_FUNCTION = lambda package: package.name == "pygments" # noqa: E731 +SKIP_FUNCTION = lambda package: True # noqa: E731 + +# Turn this to True to don't delete the virtualenvs after the tests so debugging can iterate faster. +# Remember to set to False before pushing it! +_DEBUG_MODE = True + + class PackageForTesting: name = "" import_name = "" @@ -290,7 +303,6 @@ def uninstall(self, python_cmd): "xn--eckwd4c7c.xn--zckzah", import_module_to_validate="idna.codec", test_propagation=True, - fixme_propagation_fails=True, ), PackageForTesting( "importlib-resources", @@ -479,7 +491,6 @@ def uninstall(self, python_cmd): "", import_module_to_validate="multipart.multipart", test_propagation=True, - fixme_propagation_fails=True, ), PackageForTesting( "pytz", @@ -514,7 +525,6 @@ def uninstall(self, python_cmd): "", import_module_to_validate="rsa.pkcs1", test_propagation=True, - fixme_propagation_fails=True, ), PackageForTesting( "sqlalchemy", @@ -816,22 +826,14 @@ def uninstall(self, python_cmd): ), ] -# Use this function if you want to test one or a filter number of package for debug proposes -# SKIP_FUNCTION = lambda package: package.name == "pynacl" # noqa: E731 -SKIP_FUNCTION = lambda package: True # noqa: E731 - -# Turn this to True to don't delete the virtualenvs after the tests so debugging can iterate faster. -# Remember to set to False before pushing it! -_DEBUG_MODE = False - @pytest.fixture(scope="module") def template_venv(): """ Create and configure a virtualenv template to be used for cloning in each test case """ - venv_dir = os.path.join(os.getcwd(), "template_venv") - cloned_venvs_dir = os.path.join(os.getcwd(), "cloned_venvs") + venv_dir = os.path.join(DDTRACE_PATH, "template_venv") + cloned_venvs_dir = os.path.join(DDTRACE_PATH, "cloned_venvs") os.makedirs(cloned_venvs_dir, exist_ok=True) # Create virtual environment @@ -863,7 +865,7 @@ def venv(template_venv): """ Clone the main template configured venv to each test case runs the package in a clean isolated environment """ - cloned_venvs_dir = os.path.join(os.getcwd(), "cloned_venvs") + cloned_venvs_dir = os.path.join(DDTRACE_PATH, "cloned_venvs") cloned_venv_dir = os.path.join(cloned_venvs_dir, str(uuid.uuid4())) clonevirtualenv.clone_virtualenv(template_venv, cloned_venv_dir) python_executable = os.path.join(cloned_venv_dir, "bin", "python") @@ -988,9 +990,6 @@ def test_flask_packages_propagation(package, venv, printer): _assert_propagation_results(response, package) -_INSIDE_ENV_RUNNER_PATH = os.path.join(os.path.dirname(__file__), "inside_env_runner.py") - - @pytest.mark.parametrize( "package", [package for package in PACKAGES if package.test_import and SKIP_FUNCTION(package)], From f6bc33ed6474a171c261bec8432fccb0152d706c Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 25 Oct 2024 19:47:50 -0400 Subject: [PATCH 068/372] chore(profiling): enable uwsgi tests for python 3.12 (#11177) uwsgi supports Python 3.12 from [2.0.23](https://uwsgi-docs.readthedocs.io/en/latest/Changelog-2.0.23.html) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{63292b1.txt => 10fe95f.txt} | 17 +++++++------ .../requirements/{c214c08.txt => 11aeb6a.txt} | 7 +++--- .riot/requirements/1303ffc.txt | 25 +++++++++++++++++++ .riot/requirements/133b8a6.txt | 25 +++++++++++++++++++ .riot/requirements/596b753.txt | 24 ------------------ .../requirements/{19c7eaf.txt => acb0de0.txt} | 9 ++++--- .../requirements/{162ded5.txt => c351d44.txt} | 13 +++++----- .riot/requirements/fb21cb7.txt | 24 ------------------ riotfile.py | 2 ++ tests/profiling/collector/test_stack.py | 12 +++++---- tests/profiling/test_accuracy.py | 4 --- tests/profiling/test_uwsgi.py | 12 ++------- 12 files changed, 86 insertions(+), 88 deletions(-) rename .riot/requirements/{63292b1.txt => 10fe95f.txt} (76%) rename .riot/requirements/{c214c08.txt => 11aeb6a.txt} (88%) create mode 100644 .riot/requirements/1303ffc.txt create mode 100644 .riot/requirements/133b8a6.txt delete mode 100644 .riot/requirements/596b753.txt rename .riot/requirements/{19c7eaf.txt => acb0de0.txt} (85%) rename .riot/requirements/{162ded5.txt => c351d44.txt} (82%) delete mode 100644 .riot/requirements/fb21cb7.txt diff --git a/.riot/requirements/63292b1.txt b/.riot/requirements/10fe95f.txt similarity index 76% rename from .riot/requirements/63292b1.txt rename to .riot/requirements/10fe95f.txt index 871be096b00..12611af2b08 100644 --- a/.riot/requirements/63292b1.txt +++ b/.riot/requirements/10fe95f.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/63292b1.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/10fe95f.in # -attrs==23.2.0 -coverage[toml]==7.6.0 +attrs==24.2.0 +coverage[toml]==7.6.4 gevent==23.9.0 -greenlet==3.0.3 -gunicorn[gevent]==22.0.0 +greenlet==3.1.1 +gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 @@ -16,15 +16,16 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.1 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 +uwsgi==2.0.27 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/c214c08.txt b/.riot/requirements/11aeb6a.txt similarity index 88% rename from .riot/requirements/c214c08.txt rename to .riot/requirements/11aeb6a.txt index c95c575f8f6..9107c0564a8 100644 --- a/.riot/requirements/c214c08.txt +++ b/.riot/requirements/11aeb6a.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c214c08.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/11aeb6a.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 gunicorn==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -16,10 +16,11 @@ packaging==24.1 pluggy==1.5.0 protobuf==4.22.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 +uwsgi==2.0.27 diff --git a/.riot/requirements/1303ffc.txt b/.riot/requirements/1303ffc.txt new file mode 100644 index 00000000000..433b69884e5 --- /dev/null +++ b/.riot/requirements/1303ffc.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1303ffc.in +# +attrs==24.2.0 +coverage[toml]==7.6.4 +gunicorn==23.0.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +protobuf==4.22.0 +py-cpuinfo==8.0.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-benchmark==4.0.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +uwsgi==2.0.27 diff --git a/.riot/requirements/133b8a6.txt b/.riot/requirements/133b8a6.txt new file mode 100644 index 00000000000..2f5dd08ffe7 --- /dev/null +++ b/.riot/requirements/133b8a6.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/133b8a6.in +# +attrs==24.2.0 +coverage[toml]==7.6.4 +gunicorn==23.0.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +protobuf==5.28.3 +py-cpuinfo==8.0.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-benchmark==4.0.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +uwsgi==2.0.27 diff --git a/.riot/requirements/596b753.txt b/.riot/requirements/596b753.txt deleted file mode 100644 index 06e4dd72712..00000000000 --- a/.riot/requirements/596b753.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/596b753.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -gunicorn==21.2.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -protobuf==4.25.1 -py-cpuinfo==8.0.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/19c7eaf.txt b/.riot/requirements/acb0de0.txt similarity index 85% rename from .riot/requirements/19c7eaf.txt rename to .riot/requirements/acb0de0.txt index d6822dd6d82..5717bdd07bc 100644 --- a/.riot/requirements/19c7eaf.txt +++ b/.riot/requirements/acb0de0.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/19c7eaf.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/acb0de0.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 gunicorn==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -14,12 +14,13 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -protobuf==5.28.0 +protobuf==5.28.3 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 +uwsgi==2.0.27 diff --git a/.riot/requirements/162ded5.txt b/.riot/requirements/c351d44.txt similarity index 82% rename from .riot/requirements/162ded5.txt rename to .riot/requirements/c351d44.txt index 217ce12cc68..363b32208e7 100644 --- a/.riot/requirements/162ded5.txt +++ b/.riot/requirements/c351d44.txt @@ -2,12 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/162ded5.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c351d44.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 gevent==23.9.0 -greenlet==3.0.3 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -17,15 +17,16 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 +uwsgi==2.0.27 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.2.0 diff --git a/.riot/requirements/fb21cb7.txt b/.riot/requirements/fb21cb7.txt deleted file mode 100644 index 9d5e5c6b2b6..00000000000 --- a/.riot/requirements/fb21cb7.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/fb21cb7.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -gunicorn==21.2.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -protobuf==4.22.0 -py-cpuinfo==8.0.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/riotfile.py b/riotfile.py index ec4006e3fbe..df91b4ae652 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2944,6 +2944,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): # Python 3.12 Venv( pys=select_pys(min_version="3.12"), + pkgs={"uwsgi": latest}, venvs=[ Venv( pkgs={ @@ -3090,6 +3091,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): # Python 3.12 Venv( pys=select_pys(min_version="3.12"), + pkgs={"uwsgi": latest}, venvs=[ Venv( pkgs={ diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py index b35b9bae5d6..690be4b183c 100644 --- a/tests/profiling/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -22,11 +22,13 @@ from . import test_collector -# FIXME: remove version limitation when gevent segfaults are fixed on Python 3.12 -# Python 3.11.9 is not compatible with gevent, https://github.com/python/cpython/issues/117983 -# The fix was not backported to 3.11. 3.12 got the fix but was not released yet. -# Revisit this when 3.12.5 is released to check whether tests can be enabled. -TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) and sys.version_info < (3, 11, 9) +# Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 +# https://github.com/python/cpython/issues/117983 +# The fix was not backported to 3.11. The fix was first released in 3.12.5 for +# Python 3.12. Tested with Python 3.11.8 and 3.12.5 to confirm the issue. +TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) and ( + sys.version_info < (3, 11, 9) or sys.version_info >= (3, 12, 5) +) def func1(): diff --git a/tests/profiling/test_accuracy.py b/tests/profiling/test_accuracy.py index e23317db8b1..7d94e725b72 100644 --- a/tests/profiling/test_accuracy.py +++ b/tests/profiling/test_accuracy.py @@ -1,5 +1,4 @@ # -*- encoding: utf-8 -*- -import os import time import pytest @@ -7,9 +6,6 @@ from ddtrace.internal import compat -TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) - - def spend_1(): time.sleep(1) diff --git a/tests/profiling/test_uwsgi.py b/tests/profiling/test_uwsgi.py index 1781a339868..d19a4cf4a1a 100644 --- a/tests/profiling/test_uwsgi.py +++ b/tests/profiling/test_uwsgi.py @@ -14,12 +14,10 @@ from . import utils -# uwsgi does not support Python 3.12 yet # uwsgi is not available on Windows -if sys.version_info[:2] >= (3, 12) or sys.platform == "win32": +if sys.platform == "win32": pytestmark = pytest.mark.skip -TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) THREADS_MSG = ( b"ddtrace.internal.uwsgi.uWSGIConfigError: enable-threads option must be set to true, or a positive " b"number of threads must be set" @@ -104,7 +102,7 @@ def _get_worker_pids(stdout, num_worker, num_app_started=1): def test_uwsgi_threads_processes_master(uwsgi, tmp_path, monkeypatch): filename = str(tmp_path / "uwsgi.pprof") monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = uwsgi("--enable-threads", "--master", "--processes", "2") + proc = uwsgi("--enable-threads", "--master", "--py-call-uwsgi-fork-hooks", "--processes", "2") worker_pids = _get_worker_pids(proc.stdout, 2) # Give some time to child to actually startup time.sleep(3) @@ -114,9 +112,6 @@ def test_uwsgi_threads_processes_master(uwsgi, tmp_path, monkeypatch): utils.check_pprof_file("%s.%d.1" % (filename, pid)) -# This test fails with greenlet 2: the uwsgi.atexit function that is being called and run the profiler stop procedure is -# interrupted randomly in the middle and has no time to flush out the profile. -@pytest.mark.skipif(TESTING_GEVENT, reason="Test fails with greenlet 2") def test_uwsgi_threads_processes_master_lazy_apps(uwsgi, tmp_path, monkeypatch): filename = str(tmp_path / "uwsgi.pprof") monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) @@ -130,9 +125,6 @@ def test_uwsgi_threads_processes_master_lazy_apps(uwsgi, tmp_path, monkeypatch): utils.check_pprof_file("%s.%d.1" % (filename, pid)) -# This test fails with greenlet 2: the uwsgi.atexit function that is being called and run the profiler stop procedure is -# interrupted randomly in the middle and has no time to flush out the profile. -@pytest.mark.skipif(TESTING_GEVENT, reason="Test fails with greenlet 2") def test_uwsgi_threads_processes_no_master_lazy_apps(uwsgi, tmp_path, monkeypatch): filename = str(tmp_path / "uwsgi.pprof") monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) From 72e75935cc81e5f5eaee149d0c027ed602094395 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 28 Oct 2024 08:27:03 +0100 Subject: [PATCH 069/372] chore(iast): remove DD_IAST_TELEMETRY_VERBOSITY env var usage (#11155) Code Security: Remove internal usage of `DD_IAST_TELEMETRY_VERBOSITY` env var using the `asm_config` value ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_constants.py | 21 +++++++++++++- ddtrace/appsec/_iast/_metrics.py | 29 +++++-------------- ddtrace/appsec/_iast/constants.py | 2 -- ddtrace/settings/asm.py | 3 ++ tests/appsec/iast/conftest.py | 2 +- tests/appsec/iast/test_telemetry.py | 43 ++++++++++------------------- tests/telemetry/test_writer.py | 1 + 7 files changed, 46 insertions(+), 55 deletions(-) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 4cf839d990b..513715b2c2a 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -111,6 +111,17 @@ class APPSEC(metaclass=Constant_Class): ] = "DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP" +TELEMETRY_OFF_NAME = "OFF" +TELEMETRY_DEBUG_NAME = "DEBUG" +TELEMETRY_MANDATORY_NAME = "MANDATORY" +TELEMETRY_INFORMATION_NAME = "INFORMATION" + +TELEMETRY_DEBUG_VERBOSITY = 10 +TELEMETRY_INFORMATION_VERBOSITY = 20 +TELEMETRY_MANDATORY_VERBOSITY = 30 +TELEMETRY_OFF_VERBOSITY = 40 + + class IAST(metaclass=Constant_Class): """Specific constants for IAST""" @@ -118,13 +129,21 @@ class IAST(metaclass=Constant_Class): ENV_DEBUG: Literal["DD_IAST_DEBUG"] = "DD_IAST_DEBUG" ENV_PROPAGATION_DEBUG: Literal["DD_IAST_PROPAGATION_DEBUG"] = "DD_IAST_PROPAGATION_DEBUG" ENV_REQUEST_SAMPLING: Literal["DD_IAST_REQUEST_SAMPLING"] = "DD_IAST_REQUEST_SAMPLING" - TELEMETRY_REPORT_LVL: Literal["DD_IAST_TELEMETRY_VERBOSITY"] = "DD_IAST_TELEMETRY_VERBOSITY" + ENV_TELEMETRY_REPORT_LVL: Literal["DD_IAST_TELEMETRY_VERBOSITY"] = "DD_IAST_TELEMETRY_VERBOSITY" LAZY_TAINT: Literal["_DD_IAST_LAZY_TAINT"] = "_DD_IAST_LAZY_TAINT" JSON: Literal["_dd.iast.json"] = "_dd.iast.json" ENABLED: Literal["_dd.iast.enabled"] = "_dd.iast.enabled" PATCH_MODULES: Literal["_DD_IAST_PATCH_MODULES"] = "_DD_IAST_PATCH_MODULES" DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES" SEP_MODULES: Literal[","] = "," + + METRICS_REPORT_LVLS = ( + (TELEMETRY_DEBUG_VERBOSITY, TELEMETRY_DEBUG_NAME), + (TELEMETRY_INFORMATION_VERBOSITY, TELEMETRY_INFORMATION_NAME), + (TELEMETRY_MANDATORY_VERBOSITY, TELEMETRY_MANDATORY_NAME), + (TELEMETRY_OFF_VERBOSITY, TELEMETRY_OFF_NAME), + ) + TEXT_TYPES = (str, bytes, bytearray) TAINTEABLE_TYPES = (str, bytes, bytearray, Match, BytesIO, StringIO) diff --git a/ddtrace/appsec/_iast/_metrics.py b/ddtrace/appsec/_iast/_metrics.py index 8681be0c30a..6ddc3352fe2 100644 --- a/ddtrace/appsec/_iast/_metrics.py +++ b/ddtrace/appsec/_iast/_metrics.py @@ -1,4 +1,3 @@ -import os import sys import traceback from typing import Dict @@ -6,40 +5,26 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._constants import TELEMETRY_INFORMATION_VERBOSITY +from ddtrace.appsec._constants import TELEMETRY_MANDATORY_VERBOSITY from ddtrace.appsec._deduplications import deduplication from ddtrace.appsec._iast._utils import _is_iast_debug_enabled from ddtrace.internal import telemetry from ddtrace.internal.logger import get_logger from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_IAST +from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) -TELEMETRY_OFF_NAME = "OFF" -TELEMETRY_DEBUG_NAME = "DEBUG" -TELEMETRY_MANDATORY_NAME = "MANDATORY" -TELEMETRY_INFORMATION_NAME = "INFORMATION" - -TELEMETRY_DEBUG_VERBOSITY = 10 -TELEMETRY_INFORMATION_VERBOSITY = 20 -TELEMETRY_MANDATORY_VERBOSITY = 30 -TELEMETRY_OFF_VERBOSITY = 40 - -METRICS_REPORT_LVLS = ( - (TELEMETRY_DEBUG_VERBOSITY, TELEMETRY_DEBUG_NAME), - (TELEMETRY_INFORMATION_VERBOSITY, TELEMETRY_INFORMATION_NAME), - (TELEMETRY_MANDATORY_VERBOSITY, TELEMETRY_MANDATORY_NAME), - (TELEMETRY_OFF_VERBOSITY, TELEMETRY_OFF_NAME), -) - _IAST_SPAN_METRICS: Dict[str, int] = {} def get_iast_metrics_report_lvl(*args, **kwargs): - report_lvl_name = os.environ.get(IAST.TELEMETRY_REPORT_LVL, TELEMETRY_INFORMATION_NAME).upper() + report_lvl_name = asm_config._iast_telemetry_report_lvl.upper() report_lvl = 3 - for lvl, lvl_name in METRICS_REPORT_LVLS: + for lvl, lvl_name in IAST.METRICS_REPORT_LVLS: if report_lvl_name == lvl_name: return lvl return report_lvl @@ -51,7 +36,7 @@ def wrapper(f): try: return f except Exception: - log.warning("Error reporting IAST metrics", exc_info=True) + log.warning("[IAST] Error reporting metrics", exc_info=True) return lambda: None # noqa: E731 return wrapper @@ -80,7 +65,7 @@ def _set_iast_error_metric(msg: Text) -> None: } telemetry.telemetry_writer.add_log(TELEMETRY_LOG_LEVEL.ERROR, msg, stack_trace=stack_trace, tags=tags) except Exception: - log.warning("Error reporting ASM logs metrics", exc_info=True) + log.warning("[IAST] Error reporting logs metrics", exc_info=True) @metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) diff --git a/ddtrace/appsec/_iast/constants.py b/ddtrace/appsec/_iast/constants.py index 60c864f59ec..37abb340d71 100644 --- a/ddtrace/appsec/_iast/constants.py +++ b/ddtrace/appsec/_iast/constants.py @@ -27,8 +27,6 @@ RC4_DEF = "rc4" IDEA_DEF = "idea" -DD_IAST_TELEMETRY_VERBOSITY = "DD_IAST_TELEMETRY_VERBOSITY" - DEFAULT_WEAK_HASH_ALGORITHMS = {MD5_DEF, SHA1_DEF} DEFAULT_WEAK_CIPHER_ALGORITHMS = {DES_DEF, BLOWFISH_DEF, RC2_DEF, RC4_DEF, IDEA_DEF} diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index f877c7ffc10..b15ba7b7861 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -14,6 +14,7 @@ from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import LOGIN_EVENTS_MODE +from ddtrace.appsec._constants import TELEMETRY_INFORMATION_NAME from ddtrace.constants import APPSEC_ENV from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.settings._core import report_telemetry as _report_telemetry @@ -68,6 +69,7 @@ class ASMConfig(Env): _iast_request_sampling = Env.var(float, IAST.ENV_REQUEST_SAMPLING, default=30.0) _iast_debug = Env.var(bool, IAST.ENV_DEBUG, default=False, private=True) _iast_propagation_debug = Env.var(bool, IAST.ENV_PROPAGATION_DEBUG, default=False, private=True) + _iast_telemetry_report_lvl = Env.var(str, IAST.ENV_TELEMETRY_REPORT_LVL, default=TELEMETRY_INFORMATION_NAME) _appsec_standalone_enabled = Env.var(bool, APPSEC.STANDALONE_ENV, default=False) _use_metastruct_for_triggers = False @@ -180,6 +182,7 @@ class ASMConfig(Env): "_iast_request_sampling", "_iast_debug", "_iast_propagation_debug", + "_iast_telemetry_report_lvl", "_ep_enabled", "_use_metastruct_for_triggers", "_automatic_login_events_mode", diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 793afc27c7c..06934519a77 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -77,7 +77,6 @@ class MockSpan: _trace_id_64bits = 17577308072598193742 env.update({"_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) - VulnerabilityBase._reset_cache_for_testing() with override_global_config( dict( _asm_enabled=asm_enabled, @@ -86,6 +85,7 @@ class MockSpan: _iast_request_sampling=request_sampling, ) ), override_env(env): + VulnerabilityBase._reset_cache_for_testing() _start_iast_context_and_oce(MockSpan()) weak_hash_patch() weak_cipher_patch() diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index ee4b7082526..5ed79e66b48 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -3,11 +3,12 @@ from ddtrace.appsec._common_module_patches import patch_common_modules from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._constants import TELEMETRY_DEBUG_VERBOSITY +from ddtrace.appsec._constants import TELEMETRY_INFORMATION_NAME +from ddtrace.appsec._constants import TELEMETRY_INFORMATION_VERBOSITY +from ddtrace.appsec._constants import TELEMETRY_MANDATORY_VERBOSITY from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._handlers import _on_django_patch -from ddtrace.appsec._iast._metrics import TELEMETRY_DEBUG_VERBOSITY -from ddtrace.appsec._iast._metrics import TELEMETRY_INFORMATION_VERBOSITY -from ddtrace.appsec._iast._metrics import TELEMETRY_MANDATORY_VERBOSITY from ddtrace.appsec._iast._metrics import _set_iast_error_metric from ddtrace.appsec._iast._metrics import metric_verbosity from ddtrace.appsec._iast._patch_modules import patch_iast @@ -29,7 +30,6 @@ from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.utils import asm_context from tests.utils import DummyTracer -from tests.utils import override_env from tests.utils import override_global_config @@ -61,15 +61,12 @@ def _assert_instrumented_sink(telemetry_writer, vuln_type): ], ) def test_metric_verbosity(lvl, env_lvl, expected_result): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY=env_lvl)): + with override_global_config(dict(_iast_telemetry_report_lvl=env_lvl)): assert metric_verbosity(lvl)(lambda: 1)() == expected_result -@pytest.mark.skip_iast_check_logs def test_metric_executed_sink(no_request_sampling, telemetry_writer, caplog): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): patch_iast() tracer = DummyTracer(iast_enabled=True) @@ -99,9 +96,7 @@ def test_metric_executed_sink(no_request_sampling, telemetry_writer, caplog): def test_metric_instrumented_cmdi(no_request_sampling, telemetry_writer): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): cmdi_patch() _assert_instrumented_sink(telemetry_writer, VULN_CMDI) @@ -110,9 +105,7 @@ def test_metric_instrumented_cmdi(no_request_sampling, telemetry_writer): def test_metric_instrumented_path_traversal(no_request_sampling, telemetry_writer): # We need to unpatch first because ddtrace.appsec._iast._patch_modules loads at runtime this patch function unpatch_common_modules() - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): patch_common_modules() _assert_instrumented_sink(telemetry_writer, VULN_PATH_TRAVERSAL) @@ -121,36 +114,28 @@ def test_metric_instrumented_path_traversal(no_request_sampling, telemetry_write def test_metric_instrumented_header_injection(no_request_sampling, telemetry_writer): # We need to unpatch first because ddtrace.appsec._iast._patch_modules loads at runtime this patch function header_injection_unpatch() - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): header_injection_patch() _assert_instrumented_sink(telemetry_writer, VULN_HEADER_INJECTION) def test_metric_instrumented_sqli_sqlite3(no_request_sampling, telemetry_writer): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): sqli_sqlite3_patch() _assert_instrumented_sink(telemetry_writer, VULN_SQL_INJECTION) def test_metric_instrumented_sqli_sqlalchemy_patch(no_request_sampling, telemetry_writer): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): sqli_sqlalchemy_patch() _assert_instrumented_sink(telemetry_writer, VULN_SQL_INJECTION) def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True) - ): + with override_global_config(dict(_iast_enabled=True, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME)): _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods") metrics_result = telemetry_writer._namespace._metrics_data @@ -162,8 +147,8 @@ def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): def test_metric_request_tainted(no_request_sampling, telemetry_writer): - with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( - dict(_iast_enabled=True, _iast_request_sampling=100.0) + with override_global_config( + dict(_iast_enabled=True, _iast_request_sampling=100.0, _iast_telemetry_report_lvl=TELEMETRY_INFORMATION_NAME) ): oce.reconfigure() tracer = DummyTracer(iast_enabled=True) diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index ba49d0e3103..57fbe843c37 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -370,6 +370,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python "[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}", }, {"name": "DD_IAST_REQUEST_SAMPLING", "origin": "default", "value": 30.0}, + {"name": "DD_IAST_TELEMETRY_VERBOSITY", "origin": "default", "value": "INFORMATION"}, {"name": "DD_INJECT_FORCE", "origin": "env_var", "value": True}, {"name": "DD_INSTRUMENTATION_INSTALL_ID", "origin": "default", "value": None}, {"name": "DD_INSTRUMENTATION_INSTALL_TYPE", "origin": "default", "value": None}, From 83ce1188f5f0987457664bc461283f06570760dc Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 28 Oct 2024 08:32:52 +0100 Subject: [PATCH 070/372] chore(iast): update .gitignore (#11172) Those folders are required for tests/appsec/iast_packages/test_packages.py - template_venv/ - cloned_venvs/ ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) From 4dde7b84ba21247265850310dd685d8a1b0f0c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20Paredes?= Date: Mon, 28 Oct 2024 11:21:10 +0100 Subject: [PATCH 071/372] fix(ci_visibility): strip out comments in codeowners (#11173) ## Description JIRA: https://datadoghq.atlassian.net/browse/SDTEST-1153 We do not strip the comments for the codeowners sections causing [issues like this](https://app.datadoghq.com/ci/test-runs?query=test_level:test&agg_m=count&agg_m_source=base&agg_t=count&citest_explorer_sort=@test.codeowners,desc&cols=@test.status,@test.codeowners,timestamp,@test.suite,@test.name,@duration,@test.service,@git.branch¤tTab=overview&eventStack=AgAAAZLDS54Ikm5TUgAAAAAAAAAYAAAAAEFaTERTNkJYQUFCVDNOMEllNXhGMUxmNwAAACQAAAAAMDE5MmMzNGQtNTM3Mi00NGY5LWI3OGQtM2RjNGMzODdjNjNi&fromUser=false&graphType=flamegraph&index=citest&testId=AgAAAZLDS54Ikm5TUgAAAAAAAAAYAAAAAEFaTERTNkJYQUFCVDNOMEllNXhGMUxmNwAAACQAAAAAMDE5MmMzNGQtNTM3Mi00NGY5LWI3OGQtM2RjNGMzODdjNjNi&trace=AgAAAZLDS54Ikm5TUgAAAAAAAAAYAAAAAEFaTERTNkJYQUFCVDNOMEllNXhGMUxmNwAAACQAAAAAMDE5MmMzNGQtNTM3Mi00NGY5LWI3OGQtM2RjNGMzODdjNjNi&start=1729248608094&end=1729853408094&paused=false) This PR: - Just checks for `#` and strips out to the end of the line ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Romain Komorn --- ddtrace/internal/codeowners.py | 9 ++++++--- ...x_codeowners_including_comments-82d9cb733a2c7285.yaml | 3 +++ tests/internal/test_codeowners.py | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml diff --git a/ddtrace/internal/codeowners.py b/ddtrace/internal/codeowners.py index fa1e63f7add..ead8e2f31b8 100644 --- a/ddtrace/internal/codeowners.py +++ b/ddtrace/internal/codeowners.py @@ -152,11 +152,14 @@ def parse(self) -> None: patterns = [] for line in f.readlines(): line = line.strip() + + if "#" in line: + # Strip out the comment from the line + line = line.split("#", 1)[0].strip() + if line == "": continue - # Lines starting with '#' are comments. - if line.startswith("#"): - continue + if line.startswith("[") and line.endswith("]"): # found a code owners section continue diff --git a/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml b/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml new file mode 100644 index 00000000000..3fb4bb5abd8 --- /dev/null +++ b/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml @@ -0,0 +1,3 @@ +--- +fixes: + - CI Visibility: "fixes a bug where ``CODEOWNERS`` would incorrectly fail to discard line-level trailing comments (eg: ``@code/owner # my comment`` would result in codeowners being parsed as ``@code/owner``, ``#``, ``my``, and ``comment``)" diff --git a/tests/internal/test_codeowners.py b/tests/internal/test_codeowners.py index fdf3ba28d65..5536eeea7dc 100644 --- a/tests/internal/test_codeowners.py +++ b/tests/internal/test_codeowners.py @@ -9,9 +9,12 @@ def test_invalid_codeowners(testdir): ^[invalid optional section bar.py @bars + # Inline comment case + baz.py @DataDog/the-owner # all that should be ignored """ codeowners_file = testdir.makefile("", CODEOWNERS=codeowners) c = Codeowners(path=codeowners_file.strpath) assert c.of("foo.py") == ["@default"] assert c.of("bar.py") == ["@bars"] + assert c.of("baz.py") == ["@DataDog/the-owner"] From 5051de3d2fac03415052bf3acf440685b30b5151 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:22:32 +0000 Subject: [PATCH 072/372] fix(ci_visibility): avoid git tracebacks when .git dir is absent (#11175) Fixes #10983 where the git metadata upload would log exceptions when trying to upload git metadata without a working `git` environment (eg: missing `git` executable or no `.git` directory). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Higgins --- ddtrace/internal/ci_visibility/git_client.py | 13 +++++++++++++ ...logged_errors_when_gitless-66a6cb3245314f3e.yaml | 4 ++++ tests/ci_visibility/test_ci_visibility.py | 9 +++++++++ 3 files changed, 26 insertions(+) create mode 100644 releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml diff --git a/ddtrace/internal/ci_visibility/git_client.py b/ddtrace/internal/ci_visibility/git_client.py index ae9f009dcb6..6ca8a8ef20c 100644 --- a/ddtrace/internal/ci_visibility/git_client.py +++ b/ddtrace/internal/ci_visibility/git_client.py @@ -24,6 +24,7 @@ from ddtrace.ext.git import extract_commit_sha from ddtrace.ext.git import extract_git_version from ddtrace.ext.git import extract_remote_url +from ddtrace.ext.git import extract_workspace_path from ddtrace.internal.agent import get_trace_url from ddtrace.internal.compat import JSONDecodeError from ddtrace.internal.logger import get_logger @@ -103,8 +104,20 @@ def __init__( GIT_API_BASE_PATH, ) + def _get_git_dir(self, cwd=None): + # type: (Optional[str]) -> Optional[str] + try: + return extract_workspace_path(cwd=cwd) + except ValueError: + return None + def upload_git_metadata(self, cwd=None): # type: (Optional[str]) -> None + if not self._get_git_dir(cwd=cwd): + log.debug("Missing .git directory; skipping git metadata upload") + self._metadata_upload_status.value = METADATA_UPLOAD_STATUS.FAILED # type: ignore[attr-defined] + return + self._tags = ci.tags(cwd=cwd) if self._worker is None: self._worker = Process( diff --git a/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml b/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml new file mode 100644 index 00000000000..f248c2ad640 --- /dev/null +++ b/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - CI Visibility: fixes unnecessary logging of an exception that would appear when trying to upload git metadata in + an environment without functioning git (eg: missing ``git`` binary or ``.git`` directory) diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index 4e7561a0bfb..4db43a823b4 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -843,6 +843,15 @@ def test_upload_git_metadata_upload_unnecessary(self, api_key, requests_mode): git_client.upload_git_metadata() assert git_client.wait_for_metadata_upload_status() == METADATA_UPLOAD_STATUS.UNNECESSARY + @pytest.mark.parametrize("api_key, requests_mode", api_key_requests_mode_parameters) + def test_upload_git_metadata_upload_no_git_dir(self, api_key, requests_mode): + with mock.patch.object(CIVisibilityGitClient, "_get_git_dir", mock.Mock(return_value=None)): + git_client = CIVisibilityGitClient(api_key, requests_mode) + git_client.upload_git_metadata() + + # Notably, this should _not_ raise ValueError + assert git_client.wait_for_metadata_upload_status() == METADATA_UPLOAD_STATUS.FAILED + def test_get_filtered_revisions(): with mock.patch( From b7c97b59a2b8fa471a8fe3dc30cdf3309bbc88ef Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:21:30 +0000 Subject: [PATCH 073/372] chore(ci_visibility): add Early Flake Detection to pytest v2 plugin (#11112) This adds Early Flake Detection logic for the v2 pytest plugin. Worthwhile notes: - Tests that fail during the initial setup or teardown are not retried - Counts displayed by pytest come from the `terminalreporter`'s `stats` attribute, so the `pytest_terminal_summary` hook makes some changes to move items around - Errors displayed by pytest also come from the `terminalreport`'s `stats` attribute, so error reports are temporarily copied into `failed` tests and then removed - Exit status cannot be modified by `pytest_terminal_summary`, so it is instead modified during `pytest_session_finish`. - `CIVisibility` is now disabled in `pytest_unconfigure()` because some information (eg: EFD enablement status) is required during `pytest_terminal_summary`) Side notes/changes: - the EFD process is changed to first finish the original test, rather than keep the original test unfinished - hatch envs are updated to actually use the version (before, the latest version of pytest was always being used) - ITR-related tests are no longer being run for versions of pytest that don't support ITR No release note is added since this is still pre-release. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_efd_utils.py | 396 ++++++++++++++++++ ddtrace/contrib/pytest/_plugin_v2.py | 138 ++++-- ddtrace/contrib/pytest/_retry_utils.py | 62 +++ ddtrace/contrib/pytest/_utils.py | 28 ++ ddtrace/contrib/pytest/constants.py | 2 + ddtrace/contrib/pytest/plugin.py | 2 + ddtrace/internal/ci_visibility/api/_base.py | 4 +- .../internal/ci_visibility/api/_session.py | 18 + ddtrace/internal/ci_visibility/api/_test.py | 128 +++--- ddtrace/internal/ci_visibility/recorder.py | 36 +- .../internal/test_visibility/_efd_mixins.py | 65 ++- hatch.toml | 12 +- .../api/fake_runner_efd_all_pass.py | 24 +- .../api/fake_runner_efd_faulty_session.py | 9 +- .../api/fake_runner_efd_mix_fail.py | 24 +- .../api/fake_runner_efd_mix_pass.py | 24 +- tests/ci_visibility/test_efd.py | 83 +++- tests/ci_visibility/util.py | 2 +- .../contrib/pytest/test_coverage_per_suite.py | 31 +- tests/contrib/pytest/test_pytest.py | 16 +- tests/contrib/pytest/test_pytest_efd.py | 291 +++++++++++++ 21 files changed, 1164 insertions(+), 231 deletions(-) create mode 100644 ddtrace/contrib/pytest/_efd_utils.py create mode 100644 ddtrace/contrib/pytest/_retry_utils.py create mode 100644 tests/contrib/pytest/test_pytest_efd.py diff --git a/ddtrace/contrib/pytest/_efd_utils.py b/ddtrace/contrib/pytest/_efd_utils.py new file mode 100644 index 00000000000..ca600854417 --- /dev/null +++ b/ddtrace/contrib/pytest/_efd_utils.py @@ -0,0 +1,396 @@ +import typing as t + +import _pytest +from _pytest.logging import caplog_handler_key +from _pytest.logging import caplog_records_key +import pytest + +from ddtrace.contrib.pytest._retry_utils import RetryOutcomes +from ddtrace.contrib.pytest._retry_utils import _efd_get_attempt_string +from ddtrace.contrib.pytest._retry_utils import _retry_run_when +from ddtrace.contrib.pytest._retry_utils import set_retry_num +from ddtrace.contrib.pytest._utils import PYTEST_STATUS +from ddtrace.contrib.pytest._utils import _get_test_id_from_item +from ddtrace.contrib.pytest._utils import _TestOutcome +from ddtrace.ext.test_visibility.api import TestExcInfo +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus +from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId +from ddtrace.internal.test_visibility.api import InternalTest +from ddtrace.internal.test_visibility.api import InternalTestSession + + +log = get_logger(__name__) + + +class _EFD_RETRY_OUTCOMES: + EFD_ATTEMPT_PASSED = "dd_efd_attempt_passed" + EFD_ATTEMPT_FAILED = "dd_efd_attempt_failed" + EFD_ATTEMPT_SKIPPED = "dd_efd_attempt_skipped" + EFD_FINAL_PASSED = "dd_efd_final_passed" + EFD_FINAL_FAILED = "dd_efd_final_failed" + EFD_FINAL_SKIPPED = "dd_efd_final_skipped" + EFD_FINAL_FLAKY = "dd_efd_final_flaky" + + +_EFD_FLAKY_OUTCOME = "flaky" + +_FINAL_OUTCOMES: t.Dict[EFDTestStatus, str] = { + EFDTestStatus.ALL_PASS: _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED, + EFDTestStatus.ALL_FAIL: _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED, + EFDTestStatus.ALL_SKIP: _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED, + EFDTestStatus.FLAKY: _EFD_RETRY_OUTCOMES.EFD_FINAL_FLAKY, +} + + +def efd_handle_retries( + test_id: InternalTestId, + item: pytest.Item, + when: str, + original_result: _pytest.reports.TestReport, + test_outcome: _TestOutcome, +): + # Overwrite the original result to avoid double-counting when displaying totals in final summary + if when == "call": + if test_outcome.status == TestStatus.FAIL: + original_result.outcome = _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED + elif test_outcome.status == TestStatus.PASS: + original_result.outcome = _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED + elif test_outcome.status == TestStatus.SKIP: + original_result.outcome = _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED + return + if InternalTest.get_tag(test_id, "_dd.ci.efd_setup_failed"): + log.debug("Test item %s failed during setup, will not be retried for Early Flake Detection") + return + if InternalTest.get_tag(test_id, "_dd.ci.efd_teardown_failed"): + # NOTE: tests that passed their call but failed during teardown are not retried + log.debug("Test item %s failed during teardown, will not be retried for Early Flake Detection") + return + + # If the test skipped (can happen either in setup or call depending on mark vs calling .skip()), we set the original + # status as skipped and then continue handling retries because we may not return + if test_outcome.status == TestStatus.SKIP and when in ["setup", "call"]: + original_result.outcome = _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED + # We don't return for when == call when skip happens during setup, so we need to log it and make sure the status + # of the test is set + if when == "setup": + item.ihook.pytest_runtest_logreport( + nodeid=item.nodeid, + locationm=item.location, + keywords=item.keywords, + when="setup", + longrepr=None, + outcome=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, + ) + InternalTest.mark_skip(test_id) + + efd_outcome = _efd_handle_retries(item) + + final_report = pytest.TestReport( + nodeid=item.nodeid, + location=item.location, + keywords=item.keywords, + when="call", + longrepr=None, + outcome=_FINAL_OUTCOMES[efd_outcome], + ) + item.ihook.pytest_runtest_logreport(report=final_report) + + +def efd_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) -> t.List[_pytest.reports.TestReport]: + return terminalreporter.getreports(_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED) + + +def _efd_handle_retries(item: pytest.Item) -> EFDTestStatus: + test_id = _get_test_id_from_item(item) + + while InternalTest.efd_should_retry(test_id): + retry_num = InternalTest.efd_add_retry(test_id, start_immediately=True) + + with set_retry_num(item.nodeid, retry_num): + retry_outcome = _efd_get_outcome_from_item(item) + + InternalTest.efd_finish_retry( + test_id, retry_num, retry_outcome.status, retry_outcome.skip_reason, retry_outcome.exc_info + ) + + return InternalTest.efd_get_final_status(test_id) + + +def _efd_get_outcome_from_item( + item: pytest.Item, +) -> _TestOutcome: + _outcome_status: t.Optional[TestStatus] = None + _outcome_skip_reason: t.Optional[str] = None + _outcome_exc_info: t.Optional[TestExcInfo] = None + + # _initrequest() needs to be called first because the test has already executed once + item._initrequest() + + outcomes = RetryOutcomes( + PASSED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + FAILED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + SKIPPED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, + XFAIL=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + XPASS=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + ) + + # Setup + setup_call, setup_report = _retry_run_when(item, "setup", outcomes) + if setup_report.failed: + _outcome_status = TestStatus.FAIL + if setup_call.excinfo is not None: + _outcome_exc_info = TestExcInfo(setup_call.excinfo.type, setup_call.excinfo.value, setup_call.excinfo.tb) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + if setup_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: + _outcome_status = TestStatus.SKIP + + # Call + if setup_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: + call_call, call_report = _retry_run_when(item, "call", outcomes) + if call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: + _outcome_status = TestStatus.FAIL + if call_call.excinfo is not None: + _outcome_exc_info = TestExcInfo(call_call.excinfo.type, call_call.excinfo.value, call_call.excinfo.tb) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + elif call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: + _outcome_status = TestStatus.SKIP + elif call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: + _outcome_status = TestStatus.PASS + + # Teardown does not happen if setup skipped + if not setup_report.skipped: + teardown_call, teardown_report = _retry_run_when(item, "teardown", outcomes) + # Only override the outcome if the teardown failed, otherwise defer to either setup or call outcome + if teardown_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: + _outcome_status = TestStatus.FAIL + if teardown_call.excinfo is not None: + _outcome_exc_info = TestExcInfo( + teardown_call.excinfo.type, teardown_call.excinfo.value, teardown_call.excinfo.tb + ) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + + item._initrequest() + + return _TestOutcome(status=_outcome_status, skip_reason=_outcome_skip_reason, exc_info=_outcome_exc_info) + + +def _efd_write_report_for_status( + terminalreporter: _pytest.terminal.TerminalReporter, + status_key: str, + status_text: str, + report_outcome: str, + raw_strings: t.List[str], + markedup_strings: t.List[str], + color: str, + delete_reports: bool = True, +): + reports = terminalreporter.getreports(status_key) + markup_kwargs = {color: True} + if reports: + text = f"{len(reports)} {status_text}" + raw_strings.append(text) + markedup_strings.append(terminalreporter._tw.markup(text, **markup_kwargs, bold=True)) + terminalreporter.write_sep("_", status_text.upper(), **markup_kwargs, bold=True) + for report in reports: + line = f"{terminalreporter._tw.markup(status_text.upper(), **markup_kwargs)} {report.nodeid}" + terminalreporter.write_line(line) + report.outcome = report_outcome + # Do not re-append a report if a report already exists for the item in the reports + for existing_reports in terminalreporter.stats.get(report_outcome, []): + if existing_reports.nodeid == report.nodeid: + break + else: + terminalreporter.stats.setdefault(report_outcome, []).append(report) + if delete_reports: + del terminalreporter.stats[status_key] + + +def _efd_prepare_attempts_strings( + terminalreporter: _pytest.terminal.TerminalReporter, + reports_key: str, + reports_text: str, + raw_strings: t.List[str], + markedup_strings: t.List[str], + color: str, + bold: bool = False, +): + reports = terminalreporter.getreports(reports_key) + markup_kwargs = {color: True} + if bold: + markup_kwargs["bold"] = True + if reports: + failed_attempts_text = f"{len(reports)} {reports_text}" + raw_strings.append(failed_attempts_text) + markedup_strings.append(terminalreporter._tw.markup(failed_attempts_text, **markup_kwargs)) + del terminalreporter.stats[reports_key] + + +def efd_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.TerminalReporter): + terminalreporter.write_sep("=", "Datadog Early Flake Detection", purple=True, bold=True) + # Print summary info + raw_summary_strings = [] + markedup_summary_strings = [] + + _efd_write_report_for_status( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED, + "failed", + PYTEST_STATUS.FAILED, + raw_summary_strings, + markedup_summary_strings, + color="red", + ) + + _efd_write_report_for_status( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED, + "passed", + PYTEST_STATUS.PASSED, + raw_summary_strings, + markedup_summary_strings, + color="green", + ) + + _efd_write_report_for_status( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED, + "skipped", + PYTEST_STATUS.SKIPPED, + raw_summary_strings, + markedup_summary_strings, + color="yellow", + ) + + _efd_write_report_for_status( + terminalreporter, + _EFD_FLAKY_OUTCOME, + _EFD_FLAKY_OUTCOME, + _EFD_FLAKY_OUTCOME, + raw_summary_strings, + markedup_summary_strings, + color="yellow", + delete_reports=False, + ) + + # Flaky tests could have passed their initial attempt, so they need to be removed from the passed stats to avoid + # overcounting: + flaky_node_ids = {report.nodeid for report in terminalreporter.stats.get(_EFD_FLAKY_OUTCOME, [])} + passed_reports = terminalreporter.stats.get("passed", []) + if passed_reports: + terminalreporter.stats["passed"] = [report for report in passed_reports if report.nodeid not in flaky_node_ids] + + raw_attempt_strings = [] + markedup_attempts_strings = [] + + _efd_prepare_attempts_strings( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + "failed", + raw_attempt_strings, + markedup_attempts_strings, + "red", + bold=True, + ) + _efd_prepare_attempts_strings( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + "passed", + raw_attempt_strings, + markedup_attempts_strings, + "green", + ) + _efd_prepare_attempts_strings( + terminalreporter, + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, + "skipped", + raw_attempt_strings, + markedup_attempts_strings, + "yellow", + ) + + raw_summary_string = ". ".join(raw_summary_strings) + # NOTE: find out why bold=False seems to apply to the following string, rather than the current one... + markedup_summary_string = ", ".join(markedup_summary_strings) + + if markedup_attempts_strings: + markedup_summary_string += ( + terminalreporter._tw.markup(" (total attempts: ", purple=True) + + ", ".join(markedup_attempts_strings) + + terminalreporter._tw.markup(")", purple=True) + ) + raw_summary_string += f" (total attempts: {', '.join(raw_attempt_strings)})" + + markedup_summary_string += terminalreporter._tw.markup("", purple=True, bold=True) + if markedup_summary_string.endswith("\x1b[0m"): + markedup_summary_string = markedup_summary_string[:-4] + + # Print summary counts + terminalreporter.write_sep("_", "Datadog Early Flake Detection summary", purple=True, bold=True) + + if raw_summary_string: + terminalreporter.write_sep( + " ", + markedup_summary_string, + fullwidth=terminalreporter._tw.fullwidth + (len(markedup_summary_string) - len(raw_summary_string)), + purple=True, + bold=True, + ) + else: + if InternalTestSession.efd_is_faulty_session(): + terminalreporter.write_sep( + " ", + "No tests were retried because too many were considered new.", + red=True, + bold=True, + ) + else: + terminalreporter.write_sep( + " ", + "No Early Flake Detection results.", + purple=True, + bold=True, + ) + terminalreporter.write_sep("=", purple=True, bold=True) + + +def efd_get_teststatus(report: _pytest.reports.TestReport) -> t.Optional[pytest.TestShortLogReport]: + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + "r", + (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}PASSED", {"green": True}), + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + "R", + (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}FAILED", {"yellow": True}), + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, + "s", + (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED, ".", ("EFD FINAL STATUS: PASSED", {"green": True}) + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED, "F", ("EFD FINAL STATUS: FAILED", {"red": True}) + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED: + return pytest.TestShortLogReport( + _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED, "S", ("EFD FINAL STATUS: SKIPPED", {"yellow": True}) + ) + if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_FLAKY: + # Flaky tests are the only one that have a pretty string because they are intended to be displayed in the final + # count of terminal summary + return pytest.TestShortLogReport(_EFD_FLAKY_OUTCOME, "K", ("EFD FINAL STATUS: FLAKY", {"yellow": True})) + return None diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 79818992601..73c2d0468ce 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -13,8 +13,11 @@ from ddtrace.contrib.internal.coverage.utils import _is_coverage_patched from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled +from ddtrace.contrib.pytest._retry_utils import get_retry_num +from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_module_path_from_item from ddtrace.contrib.pytest._utils import _get_names_from_item +from ddtrace.contrib.pytest._utils import _get_pytest_version_tuple from ddtrace.contrib.pytest._utils import _get_session_command from ddtrace.contrib.pytest._utils import _get_source_file_info from ddtrace.contrib.pytest._utils import _get_test_id_from_item @@ -22,6 +25,8 @@ from ddtrace.contrib.pytest._utils import _is_enabled_early from ddtrace.contrib.pytest._utils import _is_test_unskippable from ddtrace.contrib.pytest._utils import _pytest_marked_to_skip +from ddtrace.contrib.pytest._utils import _pytest_version_supports_efd +from ddtrace.contrib.pytest._utils import _TestOutcome from ddtrace.contrib.pytest.constants import FRAMEWORK from ddtrace.contrib.pytest.constants import XFAIL_REASON from ddtrace.contrib.pytest.plugin import is_enabled @@ -49,16 +54,27 @@ from ddtrace.internal.test_visibility.coverage_lines import CoverageLines -log = get_logger(__name__) +if _pytest_version_supports_efd(): + from ddtrace.contrib.pytest._efd_utils import efd_get_failed_reports + from ddtrace.contrib.pytest._efd_utils import efd_get_teststatus + from ddtrace.contrib.pytest._efd_utils import efd_handle_retries + from ddtrace.contrib.pytest._efd_utils import efd_pytest_terminal_summary_post_yield +if _get_pytest_version_tuple() >= (7, 0, 0): + from pytest import CallInfo as pytest_CallInfo + from pytest import Config as pytest_Config # noqa: F401 + from pytest import TestReport as pytest_TestReport + from pytest import TestShortLogReport as pytest_TestShortLogReport +else: + from _pytest.config import Config as pytest_Config + from _pytest.reports import TestReport as pytest_TestReport + from _pytest.reports import TestReport as pytest_TestShortLogReport + from _pytest.runner import CallInfo as pytest_CallInfo -_NODEID_REGEX = re.compile("^((?P.*)/(?P[^/]*?))::(?P.*?)$") +log = get_logger(__name__) -class _TestOutcome(t.NamedTuple): - status: t.Optional[TestStatus] = None - skip_reason: t.Optional[str] = None - exc_info: t.Optional[TestExcInfo] = None +_NODEID_REGEX = re.compile("^((?P.*)/(?P[^/]*?))::(?P.*?)$") def _handle_itr_should_skip(item, test_id) -> bool: @@ -155,7 +171,7 @@ def pytest_load_initial_conftests(early_config, parser, args): _disable_ci_visibility() -def pytest_configure(config: pytest.Config) -> None: +def pytest_configure(config: pytest_Config) -> None: try: if is_enabled(config): take_over_logger_stream_handler() @@ -172,6 +188,13 @@ def pytest_configure(config: pytest.Config) -> None: _disable_ci_visibility() +def pytest_unconfigure(config: pytest_Config) -> None: + if not is_test_visibility_enabled(): + return + + _disable_ci_visibility() + + def pytest_sessionstart(session: pytest.Session) -> None: if not is_test_visibility_enabled(): return @@ -193,6 +216,9 @@ def pytest_sessionstart(session: pytest.Session) -> None: ) InternalTestSession.start() + if InternalTestSession.efd_enabled() and not _pytest_version_supports_efd(): + log.warning("Early Flake Detection disabled: pytest version is not supported") + except Exception: # noqa: E722 log.debug("encountered error during session start, disabling Datadog CI Visibility", exc_info=True) _disable_ci_visibility() @@ -232,6 +258,10 @@ def _pytest_collection_finish(session) -> None: InternalTest.mark_itr_unskippable(test_id) InternalTestSuite.mark_itr_unskippable(suite_id) + # NOTE: EFD enablement status is already specified during service enablement + if InternalTestSession.efd_enabled() and InternalTestSession.efd_is_faulty_session(): + log.warning("Early Flake Detection disabled: too many new tests detected") + def pytest_collection_finish(session) -> None: if not is_test_visibility_enabled(): @@ -318,19 +348,11 @@ def pytest_runtest_protocol(item, nextitem) -> None: return -def _process_outcome(item, call, outcome) -> _TestOutcome: - result = outcome.get_result() - +def _process_result(item, call, result) -> _TestOutcome: test_id = _get_test_id_from_item(item) has_exception = call.excinfo is not None - # There are scenarios in which we may have already finished this item in setup or call, eg: - # - it was skipped by ITR - # - it was marked with skipif - if InternalTest.is_finished(test_id): - return _TestOutcome() - # In cases where a test was marked as XFAIL, the reason is only available during when call.when == "call", so we # add it as a tag immediately: if getattr(result, "wasxfail", None): @@ -344,7 +366,6 @@ def _process_outcome(item, call, outcome) -> _TestOutcome: # - the test passed with xfail # - we are tearing down the test # DEV NOTE: some skip scenarios (eg: skipif) have an exception during setup - if call.when != "teardown" and not (has_exception or result.failed): return _TestOutcome() @@ -385,26 +406,48 @@ def _process_outcome(item, call, outcome) -> _TestOutcome: InternalTest.set_tag(test_id, test.RESULT, test.Status.XPASS.value) return _TestOutcome(TestStatus.FAIL) + # NOTE: for EFD purposes, we need to know if the test failed during setup or teardown. + if call.when == "setup" and result.failed: + InternalTest.set_tag(test_id, "_dd.ci.efd_setup_failed", True) + elif call.when == "teardown" and result.failed: + InternalTest.set_tag(test_id, "_dd.ci.efd_teardown_failed", True) + exc_info = TestExcInfo(call.excinfo.type, call.excinfo.value, call.excinfo.tb) if call.excinfo else None return _TestOutcome(status=TestStatus.FAIL, exc_info=exc_info) -def _pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo, outcome: pytest.TestReport) -> None: +def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome: pytest_TestReport) -> None: + # When EFD retries are active, we do not want makereport to generate results, but we want to record exceptions + # since they are not available in the output of runtest_protocol + if get_retry_num(item.nodeid) is not None: + return + + original_result = outcome.get_result() + test_id = _get_test_id_from_item(item) - test_outcome = _process_outcome(item, call, outcome) + test_outcome = _process_result(item, call, original_result) + # A None value for test_outcome.status implies the test has not finished yet + # Only continue to finishing the test if the test has finished, or if tearing down the test if test_outcome.status is None and call.when != "teardown": return - InternalTest.finish(test_id, test_outcome.status, test_outcome.skip_reason, test_outcome.exc_info) + # Record a result if we haven't already recorded it: + if not InternalTest.is_finished(test_id): + InternalTest.finish(test_id, test_outcome.status, test_outcome.skip_reason, test_outcome.exc_info) + + # EFD retries tests only if their teardown succeeded to ensure the best chance they will succeed + # NOTE: this mutates the original result's outcome + if InternalTestSession.efd_enabled() and InternalTest.efd_should_retry(test_id): + return efd_handle_retries(test_id, item, call.when, original_result, test_outcome) @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> None: +def pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo) -> None: """Store outcome for tracing.""" - outcome: pytest.TestReport + outcome: pytest_TestReport outcome = yield if not is_test_visibility_enabled(): @@ -416,10 +459,47 @@ def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo) -> None: log.debug("encountered error during makereport", exc_info=True) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_terminal_summary(terminalreporter, exitstatus, config): + """Report flaky or failed tests""" + # Before yield gives us a chance to show failure reports, but they have to be in terminalreporter.stats["failed"] to + # be shown. That, however, would make them count towards the final summary, so we add them temporarily, then restore + # terminalreporter.stats["failed"] to its original size after the yield. + failed_reports_initial_size = len(terminalreporter.stats.get(PYTEST_STATUS.FAILED, [])) + + if _pytest_version_supports_efd() and InternalTestSession.efd_enabled(): + for failed_report in efd_get_failed_reports(terminalreporter): + failed_report.outcome = PYTEST_STATUS.FAILED + terminalreporter.stats.setdefault("failed", []).append(failed_report) + + yield + + # After yield gives us a chance to: + # - print our flaky test status summary + # - modify the total counts + + # Restore terminalreporter.stats["failed"] to its original size so the final summary remains correct + if failed_reports_initial_size == 0: + terminalreporter.stats.pop("failed", None) + else: + terminalreporter.stats[PYTEST_STATUS.FAILED] = terminalreporter.stats[PYTEST_STATUS.FAILED][ + :failed_reports_initial_size + ] + + # IMPORTANT: terminal summary functions mutate terminalreporter.stats + if _pytest_version_supports_efd() and InternalTestSession.efd_enabled(): + efd_pytest_terminal_summary_post_yield(terminalreporter) + + return + + def _pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: if not is_test_visibility_enabled(): return + if InternalTestSession.efd_has_failed_tests(): + session.exitstatus = pytest.ExitCode.TESTS_FAILED + invoked_by_coverage_run_status = _is_coverage_invoked_by_coverage_run() pytest_cov_status = _is_pytest_cov_enabled(session.config) if _is_coverage_patched() and (pytest_cov_status or invoked_by_coverage_run_status): @@ -429,14 +509,13 @@ def _pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: lines_pct_value = _coverage_data.get(PCT_COVERED_KEY, None) if not isinstance(lines_pct_value, float): log.warning("Tried to add total covered percentage to session span but the format was unexpected") - return - InternalTestSession.set_covered_lines_pct(lines_pct_value) + else: + InternalTestSession.set_covered_lines_pct(lines_pct_value) if ModuleCodeCollector.is_installed(): ModuleCodeCollector.uninstall() InternalTestSession.finish(force_finish_children=True) - disable_test_visibility() def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: @@ -447,8 +526,13 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: _pytest_sessionfinish(session, exitstatus) except Exception: # noqa: E722 log.debug("encountered error during session finish", exc_info=True) - # Try, again, to disable CI Visibility just in case - _disable_ci_visibility() + + +def pytest_report_teststatus( + report: pytest_TestReport, +) -> t.Optional[pytest_TestShortLogReport]: + if _pytest_version_supports_efd(): + return efd_get_teststatus(report) @pytest.hookimpl(trylast=True) diff --git a/ddtrace/contrib/pytest/_retry_utils.py b/ddtrace/contrib/pytest/_retry_utils.py new file mode 100644 index 00000000000..aa1ea727cf8 --- /dev/null +++ b/ddtrace/contrib/pytest/_retry_utils.py @@ -0,0 +1,62 @@ +from contextlib import contextmanager +from dataclasses import dataclass +import typing as t + +import _pytest +from _pytest.runner import CallInfo +import pytest + +from ddtrace.internal import core + + +@dataclass(frozen=True) +class RetryOutcomes: + PASSED: str + FAILED: str + SKIPPED: str + XFAIL: str + XPASS: str + + +def get_retry_num(nodeid: str) -> t.Optional[int]: + with core.context_with_data(f"dd-pytest-retry-{nodeid}") as ctx: + return ctx.get_item("retry_num") + + +@contextmanager +def set_retry_num(nodeid: str, retry_num: int): + with core.context_with_data(f"dd-pytest-retry-{nodeid}") as ctx: + ctx.set_item("retry_num", retry_num) + yield + + +def _efd_get_attempt_string(nodeid) -> str: + retry_number = get_retry_num(nodeid) + return "ATTEMPT {} ".format(retry_number) if retry_number is not None else "INITIAL ATTEMPT " + + +def _retry_run_when(item, when, outcomes: RetryOutcomes) -> t.Tuple[CallInfo, _pytest.reports.TestReport]: + hooks = { + "setup": item.ihook.pytest_runtest_setup, + "call": item.ihook.pytest_runtest_call, + "teardown": item.ihook.pytest_runtest_teardown, + } + hook = hooks[when] + # NOTE: we use nextitem=item here to make sure that logs don't generate a new line + if when == "teardown": + call = CallInfo.from_call( + lambda: hook(item=item, nextitem=pytest.Class.from_parent(item.session, name="forced_teardown")), when=when + ) + else: + call = CallInfo.from_call(lambda: hook(item=item), when=when) + report = pytest.TestReport.from_item_and_call(item=item, call=call) + if report.outcome == "passed": + report.outcome = outcomes.PASSED + elif report.outcome == "failed" or report.outcome == "error": + report.outcome = outcomes.FAILED + elif report.outcome == "skipped": + report.outcome = outcomes.SKIPPED + # Only log for actual test calls, or failures + if when == "call" or "passed" not in report.outcome: + item.ihook.pytest_runtest_logreport(report=report) + return call, report diff --git a/ddtrace/contrib/pytest/_utils.py b/ddtrace/contrib/pytest/_utils.py index a4a23d5eed4..ab05e14caad 100644 --- a/ddtrace/contrib/pytest/_utils.py +++ b/ddtrace/contrib/pytest/_utils.py @@ -7,9 +7,13 @@ import pytest +from ddtrace.contrib.pytest.constants import EFD_MIN_SUPPORTED_VERSION from ddtrace.contrib.pytest.constants import ITR_MIN_SUPPORTED_VERSION +from ddtrace.contrib.pytest.constants import RETRIES_MIN_SUPPORTED_VERSION +from ddtrace.ext.test_visibility.api import TestExcInfo from ddtrace.ext.test_visibility.api import TestModuleId from ddtrace.ext.test_visibility.api import TestSourceFileInfo +from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.ext.test_visibility.api import TestSuiteId from ddtrace.internal.ci_visibility.constants import ITR_UNSKIPPABLE_REASON from ddtrace.internal.ci_visibility.utils import get_source_lines_for_test_method @@ -28,6 +32,16 @@ _USE_PLUGIN_V2 = asbool(os.environ.get("_DD_CIVISIBILITY_USE_PYTEST_V2", "false")) +class _PYTEST_STATUS: + ERROR = "error" + FAILED = "failed" + PASSED = "passed" + SKIPPED = "skipped" + + +PYTEST_STATUS = _PYTEST_STATUS() + + @dataclass class TestNames: module: str @@ -147,6 +161,14 @@ def _pytest_version_supports_itr() -> bool: return _get_pytest_version_tuple() >= ITR_MIN_SUPPORTED_VERSION +def _pytest_version_supports_retries() -> bool: + return _get_pytest_version_tuple() >= RETRIES_MIN_SUPPORTED_VERSION + + +def _pytest_version_supports_efd(): + return _get_pytest_version_tuple() >= EFD_MIN_SUPPORTED_VERSION + + def _pytest_marked_to_skip(item: pytest.Item) -> bool: """Checks whether Pytest will skip an item""" if item.get_closest_marker("skip") is not None: @@ -192,3 +214,9 @@ def _is_enabled_early(early_config): return False return "--ddtrace" in early_config.invocation_params.args or early_config.getini("ddtrace") + + +class _TestOutcome(t.NamedTuple): + status: t.Optional[TestStatus] = None + skip_reason: t.Optional[str] = None + exc_info: t.Optional[TestExcInfo] = None diff --git a/ddtrace/contrib/pytest/constants.py b/ddtrace/contrib/pytest/constants.py index 73fe2c1e8ff..ccb76c06d23 100644 --- a/ddtrace/contrib/pytest/constants.py +++ b/ddtrace/contrib/pytest/constants.py @@ -6,3 +6,5 @@ XFAIL_REASON = "pytest.xfail.reason" ITR_MIN_SUPPORTED_VERSION = (7, 2, 0) +RETRIES_MIN_SUPPORTED_VERSION = (7, 0, 0) +EFD_MIN_SUPPORTED_VERSION = RETRIES_MIN_SUPPORTED_VERSION diff --git a/ddtrace/contrib/pytest/plugin.py b/ddtrace/contrib/pytest/plugin.py index 0dbf9662f84..a8da8c3a5ca 100644 --- a/ddtrace/contrib/pytest/plugin.py +++ b/ddtrace/contrib/pytest/plugin.py @@ -81,10 +81,12 @@ def pytest_addoption(parser): from ddtrace.contrib.pytest._plugin_v2 import pytest_ddtrace_get_item_suite_name # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_ddtrace_get_item_test_name # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_load_initial_conftests # noqa: F401 + from ddtrace.contrib.pytest._plugin_v2 import pytest_report_teststatus # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_runtest_makereport # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_runtest_protocol # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_sessionfinish # noqa: F401 from ddtrace.contrib.pytest._plugin_v2 import pytest_sessionstart # noqa: F401 + from ddtrace.contrib.pytest._plugin_v2 import pytest_terminal_summary # noqa: F401 else: from ddtrace.contrib.pytest._plugin_v1 import pytest_collection_modifyitems # noqa: F401 from ddtrace.contrib.pytest._plugin_v1 import pytest_configure as _versioned_pytest_configure diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index 25b95d56f8c..4593db9f110 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -68,7 +68,7 @@ class TestVisibilitySessionSettings: itr_test_skipping_level: Optional[ITR_SKIPPING_LEVEL] = None itr_correlation_id: str = "" coverage_enabled: bool = False - efd_settings: Optional[EarlyFlakeDetectionSettings] = None + efd_settings: EarlyFlakeDetectionSettings = dataclasses.field(default_factory=EarlyFlakeDetectionSettings) def __post_init__(self): if not isinstance(self.tracer, Tracer): @@ -428,11 +428,9 @@ def set_tags(self, tags: Dict[str, Any]) -> None: for tag in tags: self._tags[tag] = tags[tag] - @_require_not_finished def get_tag(self, tag_name: str) -> Any: return self._tags.get(tag_name) - @_require_not_finished def get_tags(self, tag_names: List[str]) -> Dict[str, Any]: tags = {} for tag_name in tag_names: diff --git a/ddtrace/internal/ci_visibility/api/_session.py b/ddtrace/internal/ci_visibility/api/_session.py index d9bbf498efb..dfaa160f006 100644 --- a/ddtrace/internal/ci_visibility/api/_session.py +++ b/ddtrace/internal/ci_visibility/api/_session.py @@ -18,6 +18,7 @@ from ddtrace.internal.ci_visibility.telemetry.events import record_event_created from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus log = get_logger(__name__) @@ -46,6 +47,7 @@ def __init__( self._efd_abort_reason: Optional[str] = None self._efd_is_faulty_session: Optional[bool] = None + self._efd_has_efd_failed_tests: bool = False self.set_tag(test.ITR_TEST_CODE_COVERAGE_ENABLED, session_settings.coverage_enabled) @@ -104,7 +106,12 @@ def set_covered_lines_pct(self, coverage_pct: float): def get_session(self): return self + # # EFD (Early Flake Detection) functionality + # + def efd_is_enabled(self): + return self._session_settings.efd_settings.enabled + def set_efd_abort_reason(self, abort_reason: str): self._efd_abort_reason = abort_reason @@ -134,3 +141,14 @@ def efd_is_faulty_session(self): self._efd_is_faulty_session = new_tests_pct > self._session_settings.efd_settings.faulty_session_threshold return self._efd_is_faulty_session + + def efd_has_failed_tests(self): + if (not self._session_settings.efd_settings.enabled) or self.efd_is_faulty_session(): + return False + + for _module in self._children.values(): + for _suite in _module._children.values(): + for _test in _suite._children.values(): + if _test.efd_has_retries() and _test.efd_get_final_status() == EFDTestStatus.ALL_FAIL: + return True + return False diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index 7f9316c5ca4..8a777f43058 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -11,6 +11,7 @@ from ddtrace.ext.test_visibility.api import TestExcInfo from ddtrace.ext.test_visibility.api import TestSourceFileInfo from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.ci_visibility.api._base import SPECIAL_STATUS from ddtrace.internal.ci_visibility.api._base import TestVisibilityChildItem from ddtrace.internal.ci_visibility.api._base import TestVisibilityItemBase from ddtrace.internal.ci_visibility.api._base import TestVisibilitySessionSettings @@ -23,6 +24,7 @@ from ddtrace.internal.ci_visibility.telemetry.events import record_event_created from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from ddtrace.internal.test_visibility.coverage_lines import CoverageLines @@ -67,7 +69,7 @@ def __init__( self._is_new = is_new - self._is_efd_retry = is_efd_retry + self._efd_is_retry = is_efd_retry self._efd_retries: List[TestVisibilityTest] = [] self._efd_abort_reason: Optional[str] = None self._efd_initial_finish_time_ns: Optional[int] = None @@ -88,8 +90,9 @@ def _get_hierarchy_tags(self) -> Dict[str, str]: } def _set_efd_tags(self) -> None: - if self._is_efd_retry: - self.set_tag(TEST_IS_RETRY, self._is_efd_retry) + if self._efd_is_retry: + self.set_tag(TEST_IS_RETRY, self._efd_is_retry) + if self._efd_abort_reason is not None: self.set_tag(TEST_EFD_ABORT_REASON, self._efd_abort_reason) @@ -138,8 +141,28 @@ def finish_test( self.set_tag(test.SKIP_REASON, reason) if exc_info is not None: self._exc_info = exc_info + + # When EFD is enabled, we want to track whether the test is too slow to retry + if ( + self._session_settings.efd_settings.enabled + and self.is_new() + and not self._efd_is_retry + and self._efd_should_abort() + ): + self._efd_abort_reason = "slow" + super().finish(override_finish_time=override_finish_time) + def get_status(self) -> Union[TestStatus, SPECIAL_STATUS]: + if self.efd_has_retries(): + efd_status = self.efd_get_final_status() + if efd_status in (EFDTestStatus.ALL_PASS, EFDTestStatus.FLAKY): + return TestStatus.PASS + if efd_status == EFDTestStatus.ALL_SKIP: + return TestStatus.SKIP + return TestStatus.FAIL + return super().get_status() + def count_itr_skipped(self) -> None: """Tests do not count skipping on themselves, so only count on the parent. @@ -154,6 +177,21 @@ def finish_itr_skipped(self) -> None: self.mark_itr_skipped() self.finish_test(TestStatus.SKIP) + def add_coverage_data(self, coverage_data: Dict[Path, CoverageLines]) -> None: + self._coverage_data.add_covered_files(coverage_data) + + def set_parameters(self, parameters: str) -> None: + self._parameters = parameters + self.set_tag(test.PARAMETERS, self._parameters) + + def is_new(self): + # NOTE: this is a hack because tests with parameters cannot currently be counted as new (due to EFD design + # decisions) + return self._is_new and (self._parameters is None) + + # + # EFD (Early Flake Detection) functionality + # def make_early_flake_retry_from_test(self) -> "TestVisibilityTest": if self._parameters is not None: raise ValueError("Cannot create an early flake retry from a test with parameters") @@ -170,19 +208,16 @@ def make_early_flake_retry_from_test(self) -> "TestVisibilityTest": return retry_test - def add_coverage_data(self, coverage_data: Dict[Path, CoverageLines]) -> None: - self._coverage_data.add_covered_files(coverage_data) - - def set_parameters(self, parameters: str) -> None: - self._parameters = parameters - self.set_tag(test.PARAMETERS, self._parameters) + def _efd_get_retry_test(self, retry_number: int) -> "TestVisibilityTest": + return self._efd_retries[retry_number - 1] - def is_new(self): - # NOTE: this is a hack because tests with parameters cannot currently be counted as new (due to EFD design - # decisions) - return self._is_new and (self._parameters is None) + def _efd_should_abort(self) -> bool: + # We have to use current time since the span is not yet finished + if self._span is None or self._span.start_ns is None: + raise ValueError("Test span has not started") + duration_s = (time_ns() - self._span.start_ns) / 1e9 + return duration_s > 300 - # EFD (Early Flake Detection) functionality def efd_should_retry(self): efd_settings = self._session_settings.efd_settings if not efd_settings.enabled: @@ -197,28 +232,27 @@ def efd_should_retry(self): if not self.is_new(): return False - if self._efd_initial_finish_time_ns is None: - log.debug("Early Flake Detection: efd_should_retry called but test has initial result") + if not self.is_finished(): + log.debug("Early Flake Detection: efd_should_retry called but test is not finished") return False - initial_duration_s = (self._efd_initial_finish_time_ns - self._span.start_ns) / 1e9 + duration_s = self._span.duration num_retries = len(self._efd_retries) - if initial_duration_s <= 5: + if duration_s <= 5: return num_retries < efd_settings.slow_test_retries_5s - if initial_duration_s <= 10: + if duration_s <= 10: return num_retries < efd_settings.slow_test_retries_10s - if initial_duration_s <= 30: + if duration_s <= 30: return num_retries < efd_settings.slow_test_retries_30s - if initial_duration_s <= 300: + if duration_s <= 300: return num_retries < efd_settings.slow_test_retries_5m - self._efd_abort_reason = "slow" return False - def _efd_get_retry_test(self, retry_number: int) -> "TestVisibilityTest": - return self._efd_retries[retry_number - 1] + def efd_has_retries(self) -> bool: + return len(self._efd_retries) > 0 def efd_add_retry(self, start_immediately=False) -> Optional[int]: if not self.efd_should_retry(): @@ -240,36 +274,28 @@ def efd_start_retry(self, retry_number: int) -> None: def efd_finish_retry(self, retry_number: int, status: TestStatus, exc_info: Optional[TestExcInfo] = None) -> None: self._efd_get_retry_test(retry_number).finish_test(status, exc_info=exc_info) - def efd_record_initial( - self, status: TestStatus, reason: Optional[str] = None, exc_info: Optional[TestExcInfo] = None - ) -> None: - if self._efd_initial_finish_time_ns is not None: - log.debug("Early Flake Detection: initial result already recorded") - return - if not self.is_started(): - log.debug("Test needs to have started in order to record initial result") - return + def efd_get_final_status(self) -> EFDTestStatus: + status_counts: Dict[TestStatus, int] = { + TestStatus.PASS: 0, + TestStatus.FAIL: 0, + TestStatus.SKIP: 0, + } - if reason is not None: - self.set_tag(test.SKIP_REASON, reason) - if exc_info is not None: - self._exc_info = exc_info + # NOTE: we assume that any unfinished test (eg: defaulting to failed) mean the test failed + status_counts[self._status] += 1 + for retry in self._efd_retries: + status_counts[retry._status] += 1 - self._efd_initial_finish_time_ns = time_ns() - self._status = status + expected_total = len(self._efd_retries) + 1 - def efd_get_final_status(self): - # NOTE: we look at self._status directly because the test has not been finished at the time we want to call this - # method - if (self._status == TestStatus.PASS) or any( - retry.get_status() == TestStatus.PASS for retry in self._efd_retries - ): - return TestStatus.PASS - return TestStatus.FAIL + if status_counts[TestStatus.PASS] == expected_total: + return EFDTestStatus.ALL_PASS + if status_counts[TestStatus.FAIL] == expected_total: + return EFDTestStatus.ALL_FAIL + if status_counts[TestStatus.SKIP] == expected_total: + return EFDTestStatus.ALL_SKIP + + return EFDTestStatus.FLAKY def set_efd_abort_reason(self, reason: str) -> None: self._efd_abort_reason = reason - - def efd_finish_test(self): - self.set_status(self.efd_get_final_status()) - self.finish(override_finish_time=(self._efd_initial_finish_time_ns / 1e9)) # Finish expects time in seconds diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 33dcd4635a6..2632f70a119 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -72,6 +72,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.service import Service from ddtrace.internal.test_visibility._efd_mixins import EFDTestMixin +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from ddtrace.internal.test_visibility._itr_mixins import ITRMixin from ddtrace.internal.test_visibility.coverage_lines import CoverageLines @@ -273,7 +274,7 @@ def __init__(self, tracer=None, config=None, service=None): self._api_settings.skipping_enabled, ) log.info( - "API-provided settings: early flake detection enabled: %s", + "API-provided settings: Early Flake Detection enabled: %s", self._api_settings.early_flake_detection.enabled, ) log.info("Detected configurations: %s", str(self._configurations)) @@ -504,7 +505,7 @@ def enable(cls, tracer=None, config=None, service=None): log.debug("%s enabled", cls.__name__) log.info( - "Final settings: coverage collection: %s, test skipping: %s, early flake detection: %s", + "Final settings: coverage collection: %s, test skipping: %s, Early Flake Detection: %s", cls._instance._collect_coverage_enabled, CIVisibility.test_skipping_enabled(), CIVisibility.is_efd_enabled(), @@ -833,6 +834,9 @@ def _on_discover_session( test_framework_telemetry_name = test_framework_telemetry_name or TEST_FRAMEWORKS.MANUAL efd_api_settings = CIVisibility.get_efd_api_settings() + if efd_api_settings is None: + log.debug("Could not get Early Flake Detection settings, using defaults") + efd_api_settings = EarlyFlakeDetectionSettings() session_settings = TestVisibilitySessionSettings( tracer=tracer, @@ -1247,11 +1251,21 @@ def _register_itr_handlers(): # +@_requires_civisibility_enabled +def _on_efd_is_enabled() -> bool: + return CIVisibility.get_session().efd_is_enabled() + + @_requires_civisibility_enabled def _on_efd_session_is_faulty() -> bool: return CIVisibility.get_session().efd_is_faulty_session() +@_requires_civisibility_enabled +def _on_efd_session_has_efd_failed_tests() -> bool: + return CIVisibility.get_session().efd_has_failed_tests() + + @_requires_civisibility_enabled def _on_efd_should_retry_test(test_id: InternalTestId): return CIVisibility.get_test_by_id(test_id).efd_should_retry() @@ -1275,31 +1289,19 @@ def _on_efd_finish_retry(efd_finish_args: EFDTestMixin.EFDRetryFinishArgs): @_requires_civisibility_enabled -def _on_efd_record_initial(efd_record_initial_args: EFDTestMixin.EFDRecordInitialArgs): - CIVisibility.get_test_by_id(efd_record_initial_args.test_id).efd_record_initial( - efd_record_initial_args.status, efd_record_initial_args.skip_reason, efd_record_initial_args.exc_info - ) - - -@_requires_civisibility_enabled -def _on_efd_get_final_status(test_id: InternalTestId): +def _on_efd_get_final_status(test_id: InternalTestId) -> EFDTestStatus: return CIVisibility.get_test_by_id(test_id).efd_get_final_status() -@_requires_civisibility_enabled -def _on_efd_finish_test(test_id: InternalTestId): - CIVisibility.get_test_by_id(test_id).efd_finish_test() - - def _register_efd_handlers(): log.debug("Registering EFD handlers") + core.on("test_visibility.efd.is_enabled", _on_efd_is_enabled, "is_enabled") core.on("test_visibility.efd.session_is_faulty", _on_efd_session_is_faulty, "is_faulty_session") + core.on("test_visibility.efd.session_has_failed_tests", _on_efd_session_has_efd_failed_tests, "has_failed_tests") core.on("test_visibility.efd.should_retry_test", _on_efd_should_retry_test, "should_retry_test") core.on("test_visibility.efd.add_retry", _on_efd_add_retry, "retry_number") core.on("test_visibility.efd.start_retry", _on_efd_start_retry) core.on("test_visibility.efd.finish_retry", _on_efd_finish_retry) - core.on("test_visibility.efd.finish_test", _on_efd_finish_test) - core.on("test_visibility.efd.record_initial", _on_efd_record_initial) core.on("test_visibility.efd.get_final_status", _on_efd_get_final_status, "efd_final_status") diff --git a/ddtrace/internal/test_visibility/_efd_mixins.py b/ddtrace/internal/test_visibility/_efd_mixins.py index 2cb4bc4acd7..ac49c5641c3 100644 --- a/ddtrace/internal/test_visibility/_efd_mixins.py +++ b/ddtrace/internal/test_visibility/_efd_mixins.py @@ -1,8 +1,8 @@ +from enum import Enum import typing as t from ddtrace.ext.test_visibility._utils import _catch_and_log_exceptions import ddtrace.ext.test_visibility.api as ext_api -from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal import core from ddtrace.internal.logger import get_logger from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -11,15 +11,40 @@ log = get_logger(__name__) +class EFDTestStatus(Enum): + ALL_PASS = "passed" # nosec B105 + ALL_FAIL = "failed" + ALL_SKIP = "skipped" + FLAKY = "flaky" + + class EFDSessionMixin: @staticmethod @_catch_and_log_exceptions - def is_faulty_session() -> bool: + def efd_enabled() -> bool: + log.debug("Checking if Early Flake Detection is enabled for the session") + is_enabled = core.dispatch_with_results("test_visibility.efd.is_enabled").is_enabled.value + log.debug("Early Flake Detection enabled: %s", is_enabled) + return is_enabled + + @staticmethod + @_catch_and_log_exceptions + def efd_is_faulty_session() -> bool: log.debug("Checking if session is faulty for Early Flake Detection") is_faulty_session = core.dispatch_with_results("test_visibility.efd.session_is_faulty").is_faulty_session.value log.debug("Session faulty: %s", is_faulty_session) return is_faulty_session + @staticmethod + @_catch_and_log_exceptions + def efd_has_failed_tests() -> bool: + log.debug("Checking if session has failed tests for Early Flake Detection") + has_failed_tests = core.dispatch_with_results( + "test_visibility.efd.session_has_failed_tests" + ).has_failed_tests.value + log.debug("Session has EFD failed tests: %s", has_failed_tests) + return has_failed_tests + class EFDTestMixin: @staticmethod @@ -36,34 +61,6 @@ def efd_should_retry(item_id: InternalTestId) -> bool: ).should_retry_test.value return should_retry_test - class EFDRecordInitialArgs(t.NamedTuple): - """InternalTest allows recording an initial duration (for EFD purposes)""" - - test_id: InternalTestId - status: ext_api.TestStatus - skip_reason: t.Optional[str] = None - exc_info: t.Optional[ext_api.TestExcInfo] = None - - @staticmethod - @_catch_and_log_exceptions - def efd_record_initial( - item_id: InternalTestId, - status: TestStatus, - skip_reason: t.Optional[str] = None, - exc_info: t.Optional[ext_api.TestExcInfo] = None, - ): - log.debug( - "Recording initial Early Flake Detection result for item %s: status: %s, skip_reason: %s, exc_info: %s ", - item_id, - status, - skip_reason, - exc_info, - ) - core.dispatch( - "test_visibility.efd.record_initial", - (EFDTestMixin.EFDRecordInitialArgs(item_id, status, skip_reason, exc_info),), - ) - @staticmethod @_catch_and_log_exceptions def efd_add_retry(item_id: InternalTestId, start_immediately: bool = False) -> t.Optional[int]: @@ -115,15 +112,9 @@ def efd_finish_retry( @staticmethod @_catch_and_log_exceptions - def efd_get_final_status(item_id): + def efd_get_final_status(item_id) -> EFDTestStatus: log.debug("Getting final status for item %s in Early Flake Detection", item_id) final_status = core.dispatch_with_results( "test_visibility.efd.get_final_status", (item_id,) ).efd_final_status.value return final_status - - @staticmethod - @_catch_and_log_exceptions - def efd_finish_test(item_id): - log.debug("Finishing item %s in Early Flake Detection", item_id) - core.dispatch("test_visibility.efd.finish_test", (item_id,)) diff --git a/hatch.toml b/hatch.toml index 1c5d853197b..ae0f6abafff 100644 --- a/hatch.toml +++ b/hatch.toml @@ -410,10 +410,10 @@ template = "pytest_plugin_v2" dependencies = [ "httpx", "msgpack", - "pytest", - "pytest-cov", # only used so --no-cov can be passed (conflicts with setup.cfg otherwise) "requests", "hypothesis", + "pytest{matrix:pytest}", + "pytest-cov" ] [envs.pytest_plugin_v2.env-vars] @@ -428,13 +428,13 @@ test = [ ] [[envs.pytest_plugin_v2.matrix]] -python = ["3.7", "3.9"] -pytest = ["~=6.0", "latest"] +python = ["3.7"] +pytest = ["~=6.0", "~=7.0"] [[envs.pytest_plugin_v2.matrix]] -python = ["3.10", "3.12"] -pytest = ["~=6.0", "~=7.0", "latest"] +python = ["3.9", "3.10", "3.12"] +pytest = ["~=6.0", "~=7.0", "~=8.0"] [envs.snapshot_viewer] dev-mode = false diff --git a/tests/ci_visibility/api/fake_runner_efd_all_pass.py b/tests/ci_visibility/api/fake_runner_efd_all_pass.py index ab17fb52afe..5f2e6b955b5 100644 --- a/tests/ci_visibility/api/fake_runner_efd_all_pass.py +++ b/tests/ci_visibility/api/fake_runner_efd_all_pass.py @@ -15,13 +15,14 @@ from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.test_visibility import api +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus def _hack_test_duration(test_id: api.InternalTestId, duration: float): from ddtrace.internal.ci_visibility import CIVisibility test = CIVisibility.get_test_by_id(test_id) - test._efd_initial_finish_time_ns = test._span.start_ns + duration * 1e9 + test._span.start_ns = test._span.start_ns - int(duration * 1e9) def _make_test_ids(): @@ -156,7 +157,7 @@ def run_tests(): # START TESTS - assert not api.InternalTestSession.is_faulty_session(), "Session is faulty but should not be" + assert not api.InternalTestSession.efd_is_faulty_session(), "Session is faulty but should not be" # START M1 @@ -168,9 +169,9 @@ def run_tests(): # m1_s1_t1 test expect 10 EFD retries api.InternalTest.start(m1_s1_t1_id) - api.InternalTest.efd_record_initial(m1_s1_t1_id, TestStatus.PASS) - # Hack duration: _hack_test_duration(m1_s1_t1_id, 1.23456) + api.InternalTest.mark_pass(m1_s1_t1_id) + # Hack duration: m1_s1_t1_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t1_id): @@ -179,15 +180,14 @@ def run_tests(): api.InternalTest.efd_finish_retry(m1_s1_t1_id, m1_s1_t1_retry_number, TestStatus.PASS) assert m1_s1_t1_retry_count == 10, "Expected 10 EFD retries, got %s" % m1_s1_t1_retry_count m1_s1_t1_final_status = api.InternalTest.efd_get_final_status(m1_s1_t1_id) - assert m1_s1_t1_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t1_final_status - api.InternalTest.efd_finish_test( - m1_s1_t1_id, + assert m1_s1_t1_final_status == EFDTestStatus.ALL_PASS, ( + "Expected final status to be PASS, got %s" % m1_s1_t1_final_status ) # m1_s1_t2 test: expect 5 EFD retries api.InternalTest.start(m1_s1_t2_id) - api.InternalTest.efd_record_initial(m1_s1_t2_id, TestStatus.PASS) _hack_test_duration(m1_s1_t2_id, 6.54321) + api.InternalTest.mark_pass(m1_s1_t2_id) m1_s1_t2_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t2_id): @@ -196,8 +196,9 @@ def run_tests(): api.InternalTest.efd_finish_retry(m1_s1_t2_id, m1_s1_t2_retry_number, TestStatus.PASS) assert m1_s1_t2_retry_count == 5, "Expected 5 EFD retries, got %s" % m1_s1_t2_retry_count m1_s1_t2_final_status = api.InternalTest.efd_get_final_status(m1_s1_t2_id) - assert m1_s1_t2_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t2_final_status - api.InternalTest.efd_finish_test(m1_s1_t2_id) + assert m1_s1_t2_final_status == EFDTestStatus.ALL_PASS, ( + "Expected final status to be PASS, got %s" % m1_s1_t2_final_status + ) # m1_s1_t3 test: expect no retries (is a known test) api.InternalTest.start(m1_s1_t3_id) @@ -248,10 +249,9 @@ def run_tests(): # should not be retried (over max duration) api.InternalTest.start(m2_s2_t2_id) - api.InternalTest.efd_record_initial(m2_s2_t2_id, TestStatus.PASS) _hack_test_duration(m2_s2_t2_id, 301.0) - assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: slow" api.InternalTest.mark_pass(m2_s2_t2_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: slow" # should not be retried (duration not recorded) api.InternalTest.start(m2_s2_t3_id) diff --git a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py index 85f6787c924..b6058c607fd 100644 --- a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py +++ b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py @@ -11,7 +11,6 @@ from unittest import mock from ddtrace.ext.test_visibility import api as ext_api -from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.test_visibility import api @@ -136,7 +135,7 @@ def run_tests(): # START TESTS - assert api.InternalTestSession.is_faulty_session(), "Session should be faulty" + assert api.InternalTestSession.efd_is_faulty_session(), "Session should be faulty" # START M1 @@ -147,12 +146,12 @@ def run_tests(): api.InternalTestSuite.start(m1_s1_id) api.InternalTest.start(m1_s1_t1_id) - api.InternalTest.efd_record_initial(m1_s1_t1_id, TestStatus.PASS) + api.InternalTest.mark_pass(m1_s1_t1_id) assert not api.InternalTest.efd_should_retry(m1_s1_t1_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m1_s1_t1_id) api.InternalTest.start(m1_s1_t2_id) - api.InternalTest.efd_record_initial(m1_s1_t2_id, TestStatus.PASS) + api.InternalTest.mark_pass(m1_s1_t2_id) assert not api.InternalTest.efd_should_retry(m1_s1_t2_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m1_s1_t2_id) @@ -201,7 +200,7 @@ def run_tests(): api.InternalTest.mark_skip(m2_s2_t1_id) api.InternalTest.start(m2_s2_t2_id) - api.InternalTest.efd_record_initial(m2_s2_t2_id, TestStatus.PASS) + api.InternalTest.mark_pass(m2_s2_t2_id) assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m2_s2_t2_id) diff --git a/tests/ci_visibility/api/fake_runner_efd_mix_fail.py b/tests/ci_visibility/api/fake_runner_efd_mix_fail.py index 9e6857abcff..11734f1fe83 100644 --- a/tests/ci_visibility/api/fake_runner_efd_mix_fail.py +++ b/tests/ci_visibility/api/fake_runner_efd_mix_fail.py @@ -15,13 +15,14 @@ from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.test_visibility import api +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus def _hack_test_duration(test_id: api.InternalTestId, duration: float): from ddtrace.internal.ci_visibility import CIVisibility test = CIVisibility.get_test_by_id(test_id) - test._efd_initial_finish_time_ns = test._span.start_ns + duration * 1e9 + test._span.start_ns = test._span.start_ns - int(duration * 1e9) def _make_test_ids(): @@ -156,7 +157,7 @@ def run_tests(): # START TESTS - assert not api.InternalTestSession.is_faulty_session(), "Session is faulty but should not be" + assert not api.InternalTestSession.efd_is_faulty_session(), "Session is faulty but should not be" # START M1 @@ -168,9 +169,9 @@ def run_tests(): # m1_s1_t1 test expect 10 EFD retries api.InternalTest.start(m1_s1_t1_id) - api.InternalTest.efd_record_initial(m1_s1_t1_id, TestStatus.FAIL) - # Hack duration: _hack_test_duration(m1_s1_t1_id, 1.23456) + api.InternalTest.finish(m1_s1_t1_id, TestStatus.FAIL) + # Hack duration: m1_s1_t1_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t1_id): @@ -179,15 +180,14 @@ def run_tests(): api.InternalTest.efd_finish_retry(m1_s1_t1_id, m1_s1_t1_retry_number, TestStatus.FAIL) assert m1_s1_t1_retry_count == 10, "Expected 10 EFD retries, got %s" % m1_s1_t1_retry_count m1_s1_t1_final_status = api.InternalTest.efd_get_final_status(m1_s1_t1_id) - assert m1_s1_t1_final_status == TestStatus.FAIL, "Expected final status to be FAIL, got %s" % m1_s1_t1_final_status - api.InternalTest.efd_finish_test( - m1_s1_t1_id, + assert m1_s1_t1_final_status == EFDTestStatus.ALL_FAIL, ( + "Expected final status to be FAIL, got %s" % m1_s1_t1_final_status ) # m1_s1_t2 test: expect 5 EFD retries api.InternalTest.start(m1_s1_t2_id) - api.InternalTest.efd_record_initial(m1_s1_t2_id, TestStatus.FAIL) _hack_test_duration(m1_s1_t2_id, 6.54321) + api.InternalTest.finish(m1_s1_t2_id, TestStatus.FAIL) m1_s1_t2_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t2_id): @@ -196,8 +196,9 @@ def run_tests(): api.InternalTest.efd_finish_retry(m1_s1_t2_id, m1_s1_t2_retry_number, TestStatus.FAIL) assert m1_s1_t2_retry_count == 5, "Expected 5 EFD retries, got %s" % m1_s1_t2_retry_count m1_s1_t2_final_status = api.InternalTest.efd_get_final_status(m1_s1_t2_id) - assert m1_s1_t2_final_status == TestStatus.FAIL, "Expected final status to be FAIL, got %s" % m1_s1_t2_final_status - api.InternalTest.efd_finish_test(m1_s1_t2_id) + assert m1_s1_t2_final_status == EFDTestStatus.ALL_FAIL, ( + "Expected final status to be FAIL, got %s" % m1_s1_t2_final_status + ) # m1_s1_t3 test: expect no retries (is a known test) api.InternalTest.start(m1_s1_t3_id) @@ -248,10 +249,9 @@ def run_tests(): # should not be retried (over max duration) api.InternalTest.start(m2_s2_t2_id) - api.InternalTest.efd_record_initial(m2_s2_t2_id, TestStatus.PASS) _hack_test_duration(m2_s2_t2_id, 301.0) + api.InternalTest.finish(m2_s2_t2_id, TestStatus.PASS) assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: over max duration" - api.InternalTest.mark_pass(m2_s2_t2_id) # should not be retried (duration not recorded) api.InternalTest.start(m2_s2_t3_id) diff --git a/tests/ci_visibility/api/fake_runner_efd_mix_pass.py b/tests/ci_visibility/api/fake_runner_efd_mix_pass.py index 105a3d7c0cd..1b3367b28d6 100644 --- a/tests/ci_visibility/api/fake_runner_efd_mix_pass.py +++ b/tests/ci_visibility/api/fake_runner_efd_mix_pass.py @@ -15,13 +15,14 @@ from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.test_visibility import api +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus def _hack_test_duration(test_id: api.InternalTestId, duration: float): from ddtrace.internal.ci_visibility import CIVisibility test = CIVisibility.get_test_by_id(test_id) - test._efd_initial_finish_time_ns = test._span.start_ns + duration * 1e9 + test._span.start_ns = test._span.start_ns - int(duration * 1e9) def _make_test_ids(): @@ -156,7 +157,7 @@ def run_tests(): # START TESTS - assert not api.InternalTestSession.is_faulty_session(), "Session is faulty but should not be" + assert not api.InternalTestSession.efd_is_faulty_session(), "Session is faulty but should not be" # START M1 @@ -168,9 +169,9 @@ def run_tests(): # m1_s1_t1 test expect 10 EFD retries api.InternalTest.start(m1_s1_t1_id) - api.InternalTest.efd_record_initial(m1_s1_t1_id, TestStatus.PASS) - # Hack duration: _hack_test_duration(m1_s1_t1_id, 1.23456) + api.InternalTest.finish(m1_s1_t1_id, TestStatus.PASS) + # Hack duration: m1_s1_t1_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t1_id): @@ -181,15 +182,14 @@ def run_tests(): ) assert m1_s1_t1_retry_count == 10, "Expected 10 EFD retries, got %s" % m1_s1_t1_retry_count m1_s1_t1_final_status = api.InternalTest.efd_get_final_status(m1_s1_t1_id) - assert m1_s1_t1_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t1_final_status - api.InternalTest.efd_finish_test( - m1_s1_t1_id, + assert m1_s1_t1_final_status == EFDTestStatus.FLAKY, ( + "Expected final status to be FLAKY, got %s" % m1_s1_t1_final_status ) # m1_s1_t2 test: expect 5 EFD retries api.InternalTest.start(m1_s1_t2_id) - api.InternalTest.efd_record_initial(m1_s1_t2_id, TestStatus.PASS) _hack_test_duration(m1_s1_t2_id, 6.54321) + api.InternalTest.mark_pass(m1_s1_t2_id) m1_s1_t2_retry_count = 0 while api.InternalTest.efd_should_retry(m1_s1_t2_id): @@ -200,8 +200,9 @@ def run_tests(): ) assert m1_s1_t2_retry_count == 5, "Expected 5 EFD retries, got %s" % m1_s1_t2_retry_count m1_s1_t2_final_status = api.InternalTest.efd_get_final_status(m1_s1_t2_id) - assert m1_s1_t2_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t2_final_status - api.InternalTest.efd_finish_test(m1_s1_t2_id) + assert m1_s1_t2_final_status == EFDTestStatus.FLAKY, ( + "Expected final status to be FLAKY, got %s" % m1_s1_t2_final_status + ) # m1_s1_t3 test: expect no retries (is a known test) api.InternalTest.start(m1_s1_t3_id) @@ -252,10 +253,9 @@ def run_tests(): # should not be retried (over max duration) api.InternalTest.start(m2_s2_t2_id) - api.InternalTest.efd_record_initial(m2_s2_t2_id, TestStatus.PASS) _hack_test_duration(m2_s2_t2_id, 301.0) - assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: over max duration" api.InternalTest.mark_pass(m2_s2_t2_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: over max duration" # should not be retried (duration not recorded) api.InternalTest.start(m2_s2_t3_id) diff --git a/tests/ci_visibility/test_efd.py b/tests/ci_visibility/test_efd.py index d636613270e..7a564159433 100644 --- a/tests/ci_visibility/test_efd.py +++ b/tests/ci_visibility/test_efd.py @@ -14,6 +14,7 @@ from ddtrace.internal.ci_visibility.api._suite import TestVisibilitySuite from ddtrace.internal.ci_visibility.api._test import TestVisibilityTest from ddtrace.internal.ci_visibility.telemetry.constants import TEST_FRAMEWORKS +from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from tests.utils import DummyTracer @@ -67,11 +68,9 @@ def test_efd_max_retries(self, efd_settings, efd_test_duration_s, expected_max_r mock_session.efd_is_faulty_session.return_value = False with mock.patch.multiple(efd_test, get_session=lambda *args: mock_session): efd_test.start() - - efd_test.efd_record_initial(TestStatus.PASS) - # Overwrite the test duration - efd_test._efd_initial_finish_time_ns = efd_test._span.start_ns + (efd_test_duration_s * 1e9) + efd_test._span.start_ns -= efd_test_duration_s * 1e9 + efd_test.finish_test(TestStatus.PASS) retry_count = 0 while efd_test.efd_should_retry(): @@ -84,27 +83,72 @@ def test_efd_max_retries(self, efd_settings, efd_test_duration_s, expected_max_r assert retry_count == expected_max_retries @pytest.mark.parametrize( - "test_result,retry_results,expected_status", + "test_result,retry_results,expected_statuses", ( # All pass - (TestStatus.PASS, (TestStatus.PASS, TestStatus.PASS, TestStatus.PASS), TestStatus.PASS), + ( + TestStatus.PASS, + (TestStatus.PASS, TestStatus.PASS, TestStatus.PASS), + (EFDTestStatus.ALL_PASS, TestStatus.PASS), + ), # Only original test passed - (TestStatus.PASS, (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL), TestStatus.PASS), + ( + TestStatus.PASS, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), # Only one retry passed - (TestStatus.FAIL, (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), TestStatus.PASS), + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), # Kitchen sink scenarios: - (TestStatus.FAIL, (TestStatus.PASS, TestStatus.PASS, TestStatus.PASS), TestStatus.PASS), - (TestStatus.PASS, (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), TestStatus.PASS), - (TestStatus.PASS, (TestStatus.PASS, TestStatus.SKIP, TestStatus.PASS, TestStatus.FAIL), TestStatus.PASS), - (TestStatus.FAIL, (TestStatus.PASS, TestStatus.SKIP, TestStatus.PASS, TestStatus.FAIL), TestStatus.PASS), - (TestStatus.FAIL, (TestStatus.FAIL,), TestStatus.FAIL), - (TestStatus.FAIL, (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL), TestStatus.FAIL), + ( + TestStatus.FAIL, + (TestStatus.PASS, TestStatus.PASS, TestStatus.PASS), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), + ( + TestStatus.PASS, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), + ( + TestStatus.PASS, + (TestStatus.PASS, TestStatus.SKIP, TestStatus.PASS, TestStatus.FAIL), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), + ( + TestStatus.FAIL, + (TestStatus.PASS, TestStatus.SKIP, TestStatus.PASS, TestStatus.FAIL), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), + ( + TestStatus.FAIL, + (TestStatus.PASS, TestStatus.SKIP, TestStatus.PASS, TestStatus.SKIP), + (EFDTestStatus.FLAKY, TestStatus.PASS), + ), + (TestStatus.FAIL, (TestStatus.FAIL,), (EFDTestStatus.ALL_FAIL, TestStatus.FAIL)), + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL), + (EFDTestStatus.ALL_FAIL, TestStatus.FAIL), + ), + # All skipped + ( + TestStatus.SKIP, + (TestStatus.SKIP, TestStatus.SKIP, TestStatus.SKIP), + (EFDTestStatus.ALL_SKIP, TestStatus.SKIP), + ), # No retries happened - (TestStatus.FAIL, tuple(), TestStatus.FAIL), + (TestStatus.FAIL, tuple(), (EFDTestStatus.ALL_FAIL, TestStatus.FAIL)), + (TestStatus.PASS, tuple(), (EFDTestStatus.ALL_PASS, TestStatus.PASS)), + (TestStatus.SKIP, tuple(), (EFDTestStatus.ALL_SKIP, TestStatus.SKIP)), ), ) - def test_efd_final_status(self, test_result, retry_results: t.Iterable[TestStatus], expected_status): - """Tests that the EFD API correctly reports the final status of a test""" + def test_efd_final_status(self, test_result, retry_results: t.Iterable[TestStatus], expected_statuses): + """Tests that the EFD API correctly reports the final statuses of a test""" efd_test = TestVisibilityTest( name="efd_test", session_settings=self._get_session_settings(EarlyFlakeDetectionSettings(True)), @@ -114,14 +158,15 @@ def test_efd_final_status(self, test_result, retry_results: t.Iterable[TestStatu mock_session.efd_is_faulty_session.return_value = False with mock.patch.multiple(efd_test, get_session=lambda *args: mock_session): efd_test.start() - efd_test.efd_record_initial(test_result) + efd_test.finish_test(test_result) expected_num_retry = 0 for test_result in retry_results: expected_num_retry += 1 added_retry_number = efd_test.efd_add_retry(start_immediately=True) assert added_retry_number efd_test.efd_finish_retry(added_retry_number, test_result) - assert efd_test.efd_get_final_status() == expected_status + assert efd_test.efd_get_final_status() == expected_statuses[0] + assert efd_test.get_status() == expected_statuses[1] def test_efd_does_not_retry_if_disabled(self): efd_test = TestVisibilityTest( diff --git a/tests/ci_visibility/util.py b/tests/ci_visibility/util.py index 748b416baee..7354033bb19 100644 --- a/tests/ci_visibility/util.py +++ b/tests/ci_visibility/util.py @@ -122,7 +122,7 @@ def _mock_upload_git_metadata(obj, **kwargs): side_effect=_fake_fetch_tests_to_skip, ), mock.patch( "ddtrace.internal.ci_visibility.recorder.CIVisibility._fetch_unique_tests", - side_effect=_fetch_unique_tests_side_effect(unique_test_ids), + return_value=_fetch_unique_tests_side_effect(unique_test_ids), ), mock.patch.multiple( CIVisibilityGitClient, _get_repository_url=classmethod(lambda *args, **kwargs: "git@github.com:TestDog/dd-test-py.git"), diff --git a/tests/contrib/pytest/test_coverage_per_suite.py b/tests/contrib/pytest/test_coverage_per_suite.py index c03f2cb2780..a9c985fa7b4 100644 --- a/tests/contrib/pytest/test_coverage_per_suite.py +++ b/tests/contrib/pytest/test_coverage_per_suite.py @@ -4,11 +4,9 @@ import pytest -import ddtrace from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 -from ddtrace.contrib.pytest.plugin import is_enabled +from ddtrace.contrib.pytest._utils import _pytest_version_supports_itr from ddtrace.ext.test_visibility import ITR_SKIPPING_LEVEL -from ddtrace.internal.ci_visibility import CIVisibility from ddtrace.internal.ci_visibility._api_client import ITRData from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME @@ -18,11 +16,12 @@ from tests.ci_visibility.api_client._util import _make_fqdn_suite_ids from tests.ci_visibility.util import _ci_override_env from tests.ci_visibility.util import _mock_ddconfig_test_visibility -from tests.ci_visibility.util import _patch_dummy_writer +from tests.contrib.pytest.test_pytest import PytestTestCaseBase from tests.contrib.pytest.test_pytest import _fetch_test_to_skip_side_effect -from tests.utils import TracerTestCase +pytestmark = pytest.mark.skipif(not _pytest_version_supports_itr(), reason="pytest version does not support coverage") + # TODO: investigate why pytest 3.7 does not mark the decorated function line when skipped as covered _DONT_COVER_SKIPPED_FUNC_LINE = PYTHON_VERSION_INFO <= (3, 8, 0) @@ -54,27 +53,7 @@ def _get_span_coverage_data(span, use_plugin_v2=False): } -class PytestTestCase(TracerTestCase): - @pytest.fixture(autouse=True) - def fixtures(self, testdir, monkeypatch, git_repo): - self.testdir = testdir - self.monkeypatch = monkeypatch - self.git_repo = git_repo - - def inline_run(self, *args): - """Execute test script with test tracer.""" - - class CIVisibilityPlugin: - @staticmethod - def pytest_configure(config): - if is_enabled(config): - with _patch_dummy_writer(): - assert CIVisibility.enabled - CIVisibility.disable() - CIVisibility.enable(tracer=self.tracer, config=ddtrace.config.pytest) - - return self.testdir.inline_run(*args, plugins=[CIVisibilityPlugin()]) - +class PytestTestCase(PytestTestCaseBase): def test_pytest_will_report_coverage_by_suite_with_pytest_skipped(self): self.testdir.makepyfile( ret_false=""" diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index d6db2838a42..c10a6cb311a 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -35,7 +35,10 @@ def _get_spans_from_list( - spans: t.List[ddtrace.Span], span_type: str, name: t.Optional[str] = None, status: t.Optional[str] = None + spans: t.List[ddtrace.Span], + span_type: str, + name: str = None, + status: t.Optional[str] = None, ) -> t.List[ddtrace.Span]: _names_map = { "session": ("test_session_end",), @@ -48,7 +51,7 @@ def _get_spans_from_list( raise ValueError("Cannot get session spans with a name") target_type = _names_map[span_type][0] - target_name = _names_map[span_type][1] if name else None + target_name = _names_map[span_type][1] if name is not None else None selected_spans = [] @@ -75,7 +78,7 @@ def _(): return _ -class PytestTestCase(TracerTestCase): +class PytestTestCaseBase(TracerTestCase): @pytest.fixture(autouse=True) def fixtures(self, testdir, monkeypatch, git_repo): self.testdir = testdir @@ -107,6 +110,11 @@ def pytest_configure(config): CIVisibility.enable(tracer=self.tracer, config=ddtrace.config.pytest) CIVisibility._instance._itr_meta[ITR_CORRELATION_ID_TAG_NAME] = "pytestitrcorrelationid" + @staticmethod + def pytest_unconfigure(config): + if CIVisibility.enabled: + CIVisibility.disable() + if project_dir is None: project_dir = str(self.testdir.tmpdir) @@ -128,6 +136,8 @@ def subprocess_run(self, *args, env: t.Optional[t.Dict[str, str]] = None): with _ci_override_env(_base_env): return self.testdir.runpytest_subprocess(*args) + +class PytestTestCase(PytestTestCaseBase): def test_and_emit_get_version(self): version = get_version() assert isinstance(version, str) diff --git a/tests/contrib/pytest/test_pytest_efd.py b/tests/contrib/pytest/test_pytest_efd.py new file mode 100644 index 00000000000..b034b7ad892 --- /dev/null +++ b/tests/contrib/pytest/test_pytest_efd.py @@ -0,0 +1,291 @@ +"""Tests Early Flake Detection (EFD) functionality + +The tests in this module only validate the behavior of EFD, so only counts and statuses of tests, retries, and sessions +are checked. + +- The same known tests are used to override fetching of known tests. +- The session object is patched to never be a faulty session, by default. +""" +from unittest import mock + +import pytest + +from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 +from ddtrace.contrib.pytest._utils import _pytest_version_supports_efd +from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings +from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings +from tests.ci_visibility.api_client._util import _make_fqdn_test_ids +from tests.ci_visibility.util import _fetch_unique_tests_side_effect +from tests.ci_visibility.util import _get_default_civisibility_ddconfig +from tests.contrib.pytest.test_pytest import PytestTestCaseBase +from tests.contrib.pytest.test_pytest import _get_spans_from_list +from tests.utils import override_env + + +pytestmark = pytest.mark.skipif( + not (_USE_PLUGIN_V2 and _pytest_version_supports_efd()), + reason="Early Flake Detection requires v2 of the plugin and pytest >=7.0", +) + +_KNOWN_TEST_IDS = _make_fqdn_test_ids( + [ + ("", "test_known_pass.py", "test_known_passes_01"), + ("", "test_known_pass.py", "test_known_passes_02"), + ("", "test_known_fail.py", "test_known_fails_01"), + ("", "test_known_fail.py", "test_known_fails_02"), + ] +) + +_TEST_KNOWN_PASS_CONTENT = """ +def test_known_passes_01(): + assert True +def test_known_passes_02(): + assert True +""" + +_TEST_KNOWN_FAIL_CONTENT = """ +def test_known_fails_01(): + assert False + +def test_known_fails_02(): + assert False +""" + +_TEST_NEW_PASS_CONTENT = """ +def test_new_passes_01(): + assert True +""" + +_TEST_NEW_FAIL_CONTENT = """ +def test_new_fails_01(): + assert False +""" + +_TEST_NEW_FLAKY_CONTENT = """ +last_flake = False +def test_new_flaky_01(): + global last_flake + last_flake = not last_flake + assert last_flake +""" + +_TEST_NEW_SKIP_CONTENT = """ +import pytest +@pytest.mark.skip +def test_new_skips_01(): + assert False + +def test_new_skips_02(): + pytest.skip() +""" + +_TEST_NEW_FAILS_SETUP = """ +import pytest + +@pytest.fixture +def fails_setup(): + assert False + +def test_fails_setup_01(fails_setup): + assert True +""" + +_TEST_NEW_FAILS_TEARDOWN = """ +import pytest + +@pytest.fixture +def fails_teardown(): + yield + assert False + +def test_fails_teardown_01(fails_teardown): + assert True +""" + + +class PytestEFDTestCase(PytestTestCaseBase): + @pytest.fixture(autouse=True, scope="function") + def set_up_efd(self): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._fetch_unique_tests", + return_value=_KNOWN_TEST_IDS, + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + early_flake_detection=EarlyFlakeDetectionSettings(enabled=True, faulty_session_threshold=90) + ), + ): + yield + from ddtrace.internal.ci_visibility.recorder import CIVisibility + + if CIVisibility.enabled: + CIVisibility.disable() + + def test_pytest_no_ddtrace_does_not_retry(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + rec = self.inline_run("-q") + rec.assertoutcome(passed=4, failed=3) + assert len(self.pop_spans()) == 0 + + def test_pytest_efd_no_new_tests_does_not_retry(self): + """Tests that no retries will happen if the session is faulty because all tests appear new""" + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._fetch_unique_tests", + side_effect=_fetch_unique_tests_side_effect(set()), + ): + rec = self.inline_run("--ddtrace") + rec.assertoutcome(passed=4, failed=3) + assert len(self.pop_spans()) == 14 + + def test_pytest_efd_env_var_disables_retrying(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + with override_env({"DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED": "0"}), mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig() + ): + rec = self.inline_run("--ddtrace") + rec.assertoutcome(passed=4, failed=3) + + def test_pytest_efd_env_var_does_not_override_api(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + with override_env({"DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED": "1"}), mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig() + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings(early_flake_detection=EarlyFlakeDetectionSettings(enabled=False)), + ): + rec = self.inline_run("--ddtrace") + rec.assertoutcome(passed=4, failed=3) + + def test_pytest_efd_spans(self): + """Tests that an EFD session properly does the correct number of retries and sets the correct tags""" + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + self.testdir.makepyfile(test_new_skip=_TEST_NEW_SKIP_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + rec = self.inline_run("--ddtrace", "-v") + assert rec.ret == 1 + spans = self.pop_spans() + session_span = _get_spans_from_list(spans, "session")[0] + assert session_span.get_tag("test.status") == "fail" + + module_span = _get_spans_from_list(spans, "module", "")[0] + assert module_span.get_tag("test.status") == "fail" + + suite_spans = _get_spans_from_list(spans, "suite") + assert len(suite_spans) == 6 + for suite_span in suite_spans: + suite_name = suite_span.get_tag("test.suite") + if suite_name in ("test_new_fail.py", "test_known_fail.py"): + assert suite_span.get_tag("test.status") == "fail" + elif suite_name == "test_new_skip.py": + assert suite_span.get_tag("test.status") == "skip" + else: + assert suite_span.get_tag("test.status") == "pass" + + new_fail_spans = _get_spans_from_list(spans, "test", "test_new_fails_01") + assert len(new_fail_spans) == 11 + new_fail_retries = 0 + for new_fail_span in new_fail_spans: + assert new_fail_span.get_tag("test.is_new") == "true" + if new_fail_span.get_tag("test.is_retry") == "true": + new_fail_retries += 1 + assert new_fail_retries == 10 + + new_flaky_spans = _get_spans_from_list(spans, "test", "test_new_flaky_01") + assert len(new_flaky_spans) == 11 + new_flaky_retries = 0 + for new_flaky_span in new_flaky_spans: + assert new_flaky_span.get_tag("test.is_new") == "true" + if new_flaky_span.get_tag("test.is_retry") == "true": + new_flaky_retries += 1 + assert new_flaky_retries == 10 + + new_passes_spans = _get_spans_from_list(spans, "test", "test_new_passes_01") + assert len(new_passes_spans) == 11 + new_passes_retries = 0 + for new_passes_span in new_passes_spans: + assert new_passes_span.get_tag("test.is_new") == "true" + if new_passes_span.get_tag("test.is_retry") == "true": + new_passes_retries += 1 + assert new_passes_retries == 10 + + # Skips are tested twice: once with a skip mark (skips during setup) and once using pytest.skip() in the + # test body (skips during call) + new_skips_01_spans = _get_spans_from_list(spans, "test", "test_new_skips_01") + assert len(new_skips_01_spans) == 11 + new_skips_01_retries = 0 + for new_skips_span in new_skips_01_spans: + assert new_skips_span.get_tag("test.is_new") == "true" + if new_skips_span.get_tag("test.is_retry") == "true": + new_skips_01_retries += 1 + assert new_skips_01_retries == 10 + new_skips_02_spans = _get_spans_from_list(spans, "test", "test_new_skips_02") + assert len(new_skips_02_spans) == 11 + new_skips_02_retries = 0 + for new_skips_span in new_skips_02_spans: + assert new_skips_span.get_tag("test.is_new") == "true" + if new_skips_span.get_tag("test.is_retry") == "true": + new_skips_02_retries += 1 + assert new_skips_02_retries == 10 + + assert len(spans) == 67 + + def test_pytest_efd_fails_session_when_test_fails(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_new_fail=_TEST_NEW_FAIL_CONTENT) + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + assert rec.ret == 1 + assert len(spans) == 17 + + def test_pytest_efd_passes_session_when_test_flakes(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) + self.testdir.makepyfile(test_new_flaky=_TEST_NEW_FLAKY_CONTENT) + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + assert rec.ret == 0 + assert len(spans) == 29 + + def test_pytest_efd_does_not_retry_failed_setup(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_fails_setup=_TEST_NEW_FAILS_SETUP) + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + fails_setup_spans = _get_spans_from_list(spans, "test", "test_fails_setup_01") + assert len(fails_setup_spans) == 1 + assert fails_setup_spans[0].get_tag("test.is_new") == "true" + assert fails_setup_spans[0].get_tag("test.is_retry") != "true" + assert rec.ret == 1 + assert len(spans) == 7 + + def test_pytest_efd_does_not_retry_failed_teardown(self): + self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) + self.testdir.makepyfile(test_fails_setup=_TEST_NEW_FAILS_TEARDOWN) + rec = self.inline_run("--ddtrace", "-s") + spans = self.pop_spans() + fails_teardown_spans = _get_spans_from_list(spans, "test", "test_fails_teardown_01") + assert len(fails_teardown_spans) == 1 + assert fails_teardown_spans[0].get_tag("test.is_new") == "true" + assert fails_teardown_spans[0].get_tag("test.is_retry") != "true" + assert rec.ret == 1 + assert len(spans) == 7 From 3748d6938bbe141d1424db10594221e84afb2ef0 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 28 Oct 2024 12:07:27 +0000 Subject: [PATCH 074/372] refactor(di): encapsulate common signal code (#11153) We encapsulate common signal boilerplate code to simplify the coding of signals and ensure homogeneous behaviour among them. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 51 ++++---- ddtrace/debugging/_exception/replay.py | 6 +- ddtrace/debugging/_expressions.py | 17 +-- ddtrace/debugging/_origin/span.py | 16 +-- ddtrace/debugging/_probe/model.py | 16 ++- ddtrace/debugging/_probe/remoteconfig.py | 6 +- ddtrace/debugging/_signal/collector.py | 59 +-------- ddtrace/debugging/_signal/metric_sample.py | 50 ++------ ddtrace/debugging/_signal/model.py | 117 +++++++++++++++--- ddtrace/debugging/_signal/snapshot.py | 95 ++------------ ddtrace/debugging/_signal/tracing.py | 60 ++------- tests/debugging/probe/test_remoteconfig.py | 2 +- tests/debugging/signal/test_collector.py | 78 ++++-------- tests/debugging/signal/test_model.py | 49 ++++---- tests/debugging/test_debugger.py | 22 ++-- .../test_debugger_span_decoration.py | 8 +- tests/debugging/test_encoding.py | 4 +- tests/debugging/utils.py | 15 ++- 18 files changed, 274 insertions(+), 397 deletions(-) diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 74e263fc59f..4c2369d6f42 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -45,7 +45,6 @@ from ddtrace.debugging._probe.remoteconfig import ProbeRCAdapter from ddtrace.debugging._probe.status import ProbeStatusLogger from ddtrace.debugging._signal.collector import SignalCollector -from ddtrace.debugging._signal.collector import SignalContext from ddtrace.debugging._signal.metric_sample import MetricSample from ddtrace.debugging._signal.model import Signal from ddtrace.debugging._signal.model import SignalState @@ -170,7 +169,7 @@ def remove_probe(self, probe: Probe) -> None: def has_probes(self) -> bool: return bool(self.probes) - def _open_contexts(self) -> None: + def _open_signals(self) -> None: frame = self.__frame__ assert frame is not None # nosec @@ -184,7 +183,7 @@ def _open_contexts(self) -> None: for p in self.probes.values(): (context_creators if p.__context_creator__ else context_consumers).append(p) - contexts: Deque[SignalContext] = deque() + signals: Deque[Signal] = deque() # Trigger the context creators first, so that the new context can be # consumed by the consumers. @@ -225,22 +224,32 @@ def _open_contexts(self) -> None: log.error("Unsupported probe type: %s", type(probe)) continue - contexts.append(self._collector.attach(signal)) + try: + signal.do_enter() + except Exception: + log.exception("Failed to enter signal %r", signal) + continue + signals.append(signal) # Save state on the wrapping context self.set("start_time", compat.monotonic_ns()) - self.set("contexts", contexts) + self.set("signals", signals) - def _close_contexts(self, retval=None, exc_info=(None, None, None)) -> None: + def _close_signals(self, retval=None, exc_info=(None, None, None)) -> None: end_time = compat.monotonic_ns() - contexts = self.get("contexts") - while contexts: - # Open probe signal contexts are ordered, with those that have - # created new tracing context first. We need to finalise them in - # reverse order, so we pop them from the end of the queue (LIFO). - context = contexts.pop() - context.exit(retval, exc_info, end_time - self.get("start_time")) - signal = context.signal + signals = cast(Deque[Signal], self.get("signals")) + while signals: + # Open probe signals are ordered, with those that have created new + # tracing context first. We need to finalize them in reverse order, + # so we pop them from the end of the queue (LIFO). + signal = signals.pop() + try: + signal.do_exit(retval, exc_info, end_time - self.get("start_time")) + except Exception: + log.exception("Failed to exit signal %r", signal) + continue + + self._collector.push(signal) if signal.state is SignalState.DONE: self._probe_registry.set_emitting(signal.probe) @@ -248,7 +257,7 @@ def __enter__(self) -> "DebuggerWrappingContext": super().__enter__() try: - self._open_contexts() + self._open_signals() except Exception: log.exception("Failed to open debugging contexts") @@ -256,7 +265,7 @@ def __enter__(self) -> "DebuggerWrappingContext": def __return__(self, value: T) -> T: try: - self._close_contexts(retval=value) + self._close_signals(retval=value) except Exception: log.exception("Failed to close debugging contexts from return") return super().__return__(value) @@ -265,7 +274,7 @@ def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] ) -> None: try: - self._close_contexts(exc_info=(exc_type, exc_val, exc_tb)) + self._close_signals(exc_info=(exc_type, exc_val, exc_tb)) except Exception: log.exception("Failed to close debugging contexts from exception block") super().__exit__(exc_type, exc_val, exc_tb) @@ -412,14 +421,14 @@ def _dd_debugger_hook(self, probe: Probe) -> None: log.error("Unsupported probe type: %r", type(probe)) return - signal.line() - - log.debug("[%s][P: %s] Debugger. Report signal %s", os.getpid(), os.getppid(), signal) - self.__uploader__.get_collector().push(signal) + signal.do_line() if signal.state is SignalState.DONE: self._probe_registry.set_emitting(probe) + log.debug("[%s][P: %s] Debugger. Report signal %s", os.getpid(), os.getppid(), signal) + self.__uploader__.get_collector().push(signal) + except Exception: log.error("Failed to execute probe hook", exc_info=True) diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 29536e166f9..15d03c678af 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -188,7 +188,11 @@ def on_span_exception( ) # Capture - snapshot.line() + try: + snapshot.do_line() + except Exception: + log.exception("Error capturing exception replay snapshot %r", snapshot) + continue # Collect self.__uploader__.get_collector().push(snapshot) diff --git a/ddtrace/debugging/_expressions.py b/ddtrace/debugging/_expressions.py index 64d6bcb4528..5c50a97d12c 100644 --- a/ddtrace/debugging/_expressions.py +++ b/ddtrace/debugging/_expressions.py @@ -32,6 +32,7 @@ from typing import Callable from typing import Dict from typing import List +from typing import Mapping from typing import Optional from typing import Tuple from typing import Union @@ -345,7 +346,7 @@ def _compile_predicate(self, ast: DDASTType) -> Optional[List[Instr]]: self._compile_direct_predicate(ast) or self._compile_arg_predicate(ast) or self._compile_value_source(ast) ) - def compile(self, ast: DDASTType) -> Callable[[Dict[str, Any]], Any]: + def compile(self, ast: DDASTType) -> Callable[[Mapping[str, Any]], Any]: return self._make_function(ast, ("_locals",), "") @@ -375,24 +376,24 @@ class DDExpression: __compiler__ = dd_compile dsl: str - callable: Callable[[Dict[str, Any]], Any] + callable: Callable[[Mapping[str, Any]], Any] - def eval(self, _locals): + def eval(self, scope: Mapping[str, Any]) -> Any: try: - return self.callable(_locals) + return self.callable(scope) except Exception as e: raise DDExpressionEvaluationError(self.dsl, e) from e - def __call__(self, _locals): - return self.eval(_locals) + def __call__(self, scope: Mapping[str, Any]) -> Any: + return self.eval(scope) @classmethod - def on_compiler_error(cls, dsl: str, exc: Exception) -> Callable[[Dict[str, Any]], Any]: + def on_compiler_error(cls, dsl: str, exc: Exception) -> Callable[[Mapping[str, Any]], Any]: log.error("Cannot compile expression: %s", dsl, exc_info=True) return _invalid_expression @classmethod - def compile(cls, expr: Dict[str, Any]) -> "DDExpression": + def compile(cls, expr: Mapping[str, Any]) -> "DDExpression": ast = expr["json"] dsl = expr["dsl"] diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index 173cb40b128..9f0433cbca0 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -19,10 +19,10 @@ from ddtrace.debugging._probe.model import LiteralTemplateSegment from ddtrace.debugging._probe.model import LogFunctionProbe from ddtrace.debugging._probe.model import LogLineProbe -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod -from ddtrace.debugging._signal.collector import SignalContext +from ddtrace.debugging._probe.model import ProbeEvalTiming # from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.debugging._signal.model import Signal from ddtrace.ext import EXIT_SPAN_TYPES from ddtrace.internal import compat from ddtrace.internal import core @@ -63,7 +63,7 @@ def build(cls, name: str, module: str, function: str) -> "EntrySpanProbe": tags={}, module=module, func_qname=function, - evaluate_at=ProbeEvaluateTimingForMethod.ENTER, + evaluate_at=ProbeEvalTiming.ENTRY, template=message, segments=[LiteralTemplateSegment(message)], take_snapshot=True, @@ -174,21 +174,21 @@ def __enter__(self): return self - def _close_context(self, retval=None, exc_info=(None, None, None)): + def _close_signal(self, retval=None, exc_info=(None, None, None)): try: - context: SignalContext = self.get("context") + signal: Signal = t.cast(Signal, self.get("signal")) except KeyError: # No snapshot was created return - context.exit(retval, exc_info, compat.monotonic_ns() - self.get("start_time")) + signal.do_exit(retval, exc_info, compat.monotonic_ns() - self.get("start_time")) def __return__(self, retval): - self._close_context(retval=retval) + self._close_signal(retval=retval) return super().__return__(retval) def __exit__(self, exc_type, exc_value, traceback): - self._close_context(exc_info=(exc_type, exc_value, traceback)) + self._close_signal(exc_info=(exc_type, exc_value, traceback)) super().__exit__(exc_type, exc_value, traceback) diff --git a/ddtrace/debugging/_probe/model.py b/ddtrace/debugging/_probe/model.py index 53cd669d251..6f989d627f4 100644 --- a/ddtrace/debugging/_probe/model.py +++ b/ddtrace/debugging/_probe/model.py @@ -156,9 +156,9 @@ def location(self): return (maybe_stringify(self.resolved_source_file), self.line) -class ProbeEvaluateTimingForMethod(str, Enum): +class ProbeEvalTiming(str, Enum): DEFAULT = "DEFAULT" - ENTER = "ENTER" + ENTRY = "ENTRY" EXIT = "EXIT" @@ -166,12 +166,16 @@ class ProbeEvaluateTimingForMethod(str, Enum): class FunctionLocationMixin(ProbeLocationMixin): module: str = field(compare=False) func_qname: str = field(compare=False) - evaluate_at: ProbeEvaluateTimingForMethod def location(self): return (self.module, self.func_qname) +@dataclass +class TimingMixin(AbstractProbeMixIn): + evaluate_at: ProbeEvalTiming + + class MetricProbeKind(str, Enum): COUNTER = "COUNT" GAUGE = "GAUGE" @@ -192,7 +196,7 @@ class MetricLineProbe(Probe, LineLocationMixin, MetricProbeMixin, ProbeCondition @dataclass -class MetricFunctionProbe(Probe, FunctionLocationMixin, MetricProbeMixin, ProbeConditionMixin): +class MetricFunctionProbe(Probe, FunctionLocationMixin, TimingMixin, MetricProbeMixin, ProbeConditionMixin): pass @@ -245,7 +249,7 @@ class LogLineProbe(Probe, LineLocationMixin, LogProbeMixin, ProbeConditionMixin, @dataclass -class LogFunctionProbe(Probe, FunctionLocationMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): +class LogFunctionProbe(Probe, FunctionLocationMixin, TimingMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): pass @@ -288,7 +292,7 @@ class SpanDecorationLineProbe(Probe, LineLocationMixin, SpanDecorationMixin): @dataclass -class SpanDecorationFunctionProbe(Probe, FunctionLocationMixin, SpanDecorationMixin): +class SpanDecorationFunctionProbe(Probe, FunctionLocationMixin, TimingMixin, SpanDecorationMixin): pass diff --git a/ddtrace/debugging/_probe/remoteconfig.py b/ddtrace/debugging/_probe/remoteconfig.py index 31c6a2a42e3..763c6516db5 100644 --- a/ddtrace/debugging/_probe/remoteconfig.py +++ b/ddtrace/debugging/_probe/remoteconfig.py @@ -24,7 +24,7 @@ from ddtrace.debugging._probe.model import MetricFunctionProbe from ddtrace.debugging._probe.model import MetricLineProbe from ddtrace.debugging._probe.model import Probe -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import ProbeEvalTiming from ddtrace.debugging._probe.model import ProbeType from ddtrace.debugging._probe.model import SpanDecoration from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe @@ -33,6 +33,7 @@ from ddtrace.debugging._probe.model import SpanFunctionProbe from ddtrace.debugging._probe.model import StringTemplate from ddtrace.debugging._probe.model import TemplateSegment +from ddtrace.debugging._probe.model import TimingMixin from ddtrace.debugging._probe.status import ProbeStatusLogger from ddtrace.debugging._redaction import DDRedactedExpression from ddtrace.internal.logger import get_logger @@ -103,7 +104,8 @@ def build(cls, args: Dict[str, Any], attribs: Dict[str, Any]) -> Any: args["module"] = where.get("type") or where["typeName"] args["func_qname"] = where.get("method") or where["methodName"] - args["evaluate_at"] = ProbeEvaluateTimingForMethod[attribs.get("evaluateAt", "DEFAULT")] + if isinstance(cls.__function_class__, TimingMixin): + args["evaluate_at"] = ProbeEvalTiming[attribs.get("evaluateAt", "DEFAULT")] return cls.__function_class__(**args) diff --git a/ddtrace/debugging/_signal/collector.py b/ddtrace/debugging/_signal/collector.py index 74938c71ea6..461e8ff1af6 100644 --- a/ddtrace/debugging/_signal/collector.py +++ b/ddtrace/debugging/_signal/collector.py @@ -2,7 +2,6 @@ from typing import Any from typing import Callable from typing import List -from typing import Optional from typing import Tuple from ddtrace.debugging._encoding import BufferedEncoder @@ -21,59 +20,13 @@ meter = metrics.get_meter("signal.collector") -NO_RETURN_VALUE = object() - - -class SignalContext: - """Debugger signal context manager. - - This is used to capture data for function invocations. The SignalContext - call ``Signal.enter`` on entry ``Signal.exit`` on function return. - - The handler is triggered just after ``Signal.exit`` returns. - """ - - def __init__( - self, - signal: Signal, - handler: Callable[[Signal], None], - ) -> None: - self._on_exit_handler = handler - self.signal = signal - self.return_value: Any = NO_RETURN_VALUE - self.duration: Optional[int] = None - - self.signal.enter() - - def exit(self, retval: Any, exc_info: ExcInfoType, duration_ns: int) -> None: - """Exit the snapshot context. - - The arguments are used to record either the return value or the exception, and - the duration of the wrapped call. - """ - self.return_value = retval - self.duration = duration_ns - - return self.__exit__(*exc_info) - - def __enter__(self): - return self - - def __exit__(self, *exc_info: ExcInfoType) -> None: - self.signal.exit(self.return_value, exc_info, self.duration) - self._on_exit_handler(self.signal) - - class SignalCollector(object): """Debugger signal collector. This is used to collect and encode signals emitted by probes as soon as requested. The ``push`` method is intended to be called after a line-level signal is fully emitted, and information is available and ready to be - encoded, or the signal status indicate it should be skipped. For function - instrumentation (e.g. function probes), we use the ``attach`` method to - create a ``SignalContext`` instance that can be used to capture additional - data, such as the return value of the wrapped function. + encoded, or the signal status indicate it should be skipped. """ def __init__(self, encoder: BufferedEncoder) -> None: @@ -90,13 +43,13 @@ def _enqueue(self, log_signal: LogSignal) -> None: meter.increment("encoder.buffer.full") def push(self, signal: Signal) -> None: - if signal.state == SignalState.SKIP_COND: + if signal.state is SignalState.SKIP_COND: meter.increment("skip", tags={"cause": "cond", "probe_id": signal.probe.probe_id}) elif signal.state in {SignalState.SKIP_COND_ERROR, SignalState.COND_ERROR}: meter.increment("skip", tags={"cause": "cond_error", "probe_id": signal.probe.probe_id}) - elif signal.state == SignalState.SKIP_RATE: + elif signal.state is SignalState.SKIP_RATE: meter.increment("skip", tags={"cause": "rate", "probe_id": signal.probe.probe_id}) - elif signal.state == SignalState.DONE: + elif signal.state is SignalState.DONE: meter.increment("signal", tags={"probe_id": signal.probe.probe_id}) if ( @@ -111,7 +64,3 @@ def push(self, signal: Signal) -> None: log.debug( "Skipping signal %s (has message: %s)", signal, isinstance(signal, LogSignal) and signal.has_message() ) - - def attach(self, signal: Signal) -> SignalContext: - """Collect via a probe signal context manager.""" - return SignalContext(signal, lambda e: self.push(e)) diff --git a/ddtrace/debugging/_signal/metric_sample.py b/ddtrace/debugging/_signal/metric_sample.py index c14f0173734..f8bebc17d83 100644 --- a/ddtrace/debugging/_signal/metric_sample.py +++ b/ddtrace/debugging/_signal/metric_sample.py @@ -4,12 +4,9 @@ from typing import cast from ddtrace.debugging._metrics import probe_metrics -from ddtrace.debugging._probe.model import MetricFunctionProbe from ddtrace.debugging._probe.model import MetricProbeKind from ddtrace.debugging._probe.model import MetricProbeMixin -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod from ddtrace.debugging._signal.model import LogSignal -from ddtrace.debugging._signal.model import SignalState from ddtrace.internal.metrics import Metrics @@ -19,53 +16,22 @@ class MetricSample(LogSignal): meter: Metrics.Meter = field(default_factory=lambda: probe_metrics.get_meter("probe")) - def enter(self) -> None: - if not isinstance(self.probe, MetricFunctionProbe): - return + def enter(self, scope) -> None: + self.sample(scope) - probe = self.probe + def exit(self, retval, exc_info, duration, scope) -> None: + self.sample(scope) - if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: - return + def line(self, scope) -> None: + self.sample(scope) - _args = dict(self.args) if self.args else {} - if not self._eval_condition(_args): - return - - self.sample(_args) - self.state = SignalState.DONE - - def exit(self, retval, exc_info, duration) -> None: - if not isinstance(self.probe, MetricFunctionProbe): - return - - probe = self.probe - full_scope = self.get_full_scope(retval, exc_info, duration) - - if probe.evaluate_at is not ProbeEvaluateTimingForMethod.EXIT: - return - if not self._eval_condition(full_scope): - return - - self.sample(full_scope) - self.state = SignalState.DONE - - def line(self) -> None: - frame = self.frame - - if not self._eval_condition(frame.f_locals): - return - - self.sample(frame.f_locals) - self.state = SignalState.DONE - - def sample(self, _locals) -> None: + def sample(self, scope) -> None: tags = self.probe.tags probe = cast(MetricProbeMixin, self.probe) assert probe.kind is not None and probe.name is not None # nosec - value = float(probe.value(_locals)) if probe.value is not None else 1 + value = float(probe.value(scope)) if probe.value is not None else 1 # TODO[perf]: We know the tags in advance so we can avoid the # list comprehension. diff --git a/ddtrace/debugging/_signal/model.py b/ddtrace/debugging/_signal/model.py index 64cb0bff555..a03b157adde 100644 --- a/ddtrace/debugging/_signal/model.py +++ b/ddtrace/debugging/_signal/model.py @@ -7,6 +7,7 @@ import time from types import FrameType from typing import Any +from typing import ClassVar from typing import Dict from typing import List from typing import Mapping @@ -22,6 +23,9 @@ from ddtrace.debugging._probe.model import LineLocationMixin from ddtrace.debugging._probe.model import Probe from ddtrace.debugging._probe.model import ProbeConditionMixin +from ddtrace.debugging._probe.model import ProbeEvalTiming +from ddtrace.debugging._probe.model import RateLimitMixin +from ddtrace.debugging._probe.model import TimingMixin from ddtrace.debugging._safety import get_args from ddtrace.internal.compat import ExcInfoType from ddtrace.internal.rate_limiter import RateLimitExceeded @@ -48,8 +52,23 @@ class Signal(abc.ABC): Used to model the data carried by the signal emitted by a probe when it is triggered. + + Probes with an evaluation timing will trigger the following logic: + + - If the timing is on ENTRY, the probe will trigger on entry and on exit, + but the condition and rate limiter will only be evaluated on entry. + + - If the timing is on EXIT, the probe will trigger on exit only, and the + condition and rate limiter will be evaluated on exit. + + - IF the timing is DEFAULT, the probe will trigger on the default timing for + the signal. + + - If the probe has no timing, it defaults to the ENTRY timing. """ + __default_timing__: ClassVar[ProbeEvalTiming] = ProbeEvalTiming.EXIT + probe: Probe frame: FrameType thread: Thread @@ -59,9 +78,21 @@ class Signal(abc.ABC): timestamp: float = field(default_factory=time.time) uuid: str = field(default_factory=lambda: str(uuid4()), init=False) - def _eval_condition(self, scope: Optional[Mapping[str, Any]] = None) -> bool: + def __post_init__(self): + probe = self.probe + if isinstance(probe, TimingMixin): + eval_at = probe.evaluate_at + self._timing = self.__default_timing__ if eval_at is ProbeEvalTiming.DEFAULT else eval_at + else: + self._timing = ProbeEvalTiming.ENTRY + + def _eval_condition(self, scope: Mapping[str, Any]) -> bool: """Evaluate the probe condition against the collected frame.""" - probe = cast(ProbeConditionMixin, self.probe) + probe = self.probe + if not isinstance(probe, ProbeConditionMixin): + # The probe has no condition, so it should always trigger. + return True + condition = probe.condition if condition is None: return True @@ -81,35 +112,91 @@ def _eval_condition(self, scope: Optional[Mapping[str, Any]] = None) -> bool: return False - def get_full_scope(self, retval: Any, exc_info: ExcInfoType, duration: float) -> Mapping[str, Any]: - frame = self.frame - extra: Dict[str, Any] = {"@duration": duration / 1e6} # milliseconds + def _rate_limit_exceeded(self) -> bool: + """Evaluate the probe rate limiter.""" + probe = self.probe + if not isinstance(probe, RateLimitMixin): + # We don't have a rate limiter, so no rate was exceeded. + return False - exc = exc_info[1] - if exc is not None: - extra["@exception"] = exc - else: - extra["@return"] = retval + exceeded = probe.limiter.limit() is RateLimitExceeded + if exceeded: + self.state = SignalState.SKIP_RATE - # Include the frame locals and globals. - return ChainMap(extra, frame.f_locals, frame.f_globals) + return exceeded @property def args(self): return dict(get_args(self.frame)) @abc.abstractmethod - def enter(self): + def enter(self, scope: Mapping[str, Any]) -> None: pass @abc.abstractmethod - def exit(self, retval, exc_info, duration): + def exit(self, retval: Any, exc_info: ExcInfoType, duration: int, scope: Mapping[str, Any]) -> None: pass @abc.abstractmethod - def line(self): + def line(self, scope: Mapping[str, Any]) -> None: pass + def do_enter(self) -> None: + if self._timing is not ProbeEvalTiming.ENTRY: + return + + scope = ChainMap(self.args, self.frame.f_globals) + if not self._eval_condition(scope): + return + + if self._rate_limit_exceeded(): + return + + self.enter(scope) + + def do_exit(self, retval: Any, exc_info: ExcInfoType, duration: int) -> None: + if self.state is not SignalState.NONE: + # The signal has already been handled and move to a final state + return + + frame = self.frame + extra: Dict[str, Any] = {"@duration": duration / 1e6} # milliseconds + + exc = exc_info[1] + if exc is not None: + extra["@exception"] = exc + else: + extra["@return"] = retval + + scope = ChainMap(extra, frame.f_locals, frame.f_globals) + + if self._timing is ProbeEvalTiming.EXIT: + # We only evaluate the condition and the rate limiter on exit if it + # is a probe with timing on exit + if not self._eval_condition(scope): + return + + if self._rate_limit_exceeded(): + return + + self.exit(retval, cast(ExcInfoType, exc_info), duration or 0, scope) + + self.state = SignalState.DONE + + def do_line(self) -> None: + frame = self.frame + scope = ChainMap(frame.f_locals, frame.f_globals) + + if not self._eval_condition(scope): + return + + if self._rate_limit_exceeded(): + return + + self.line(scope) + + self.state = SignalState.DONE + @dataclass class LogSignal(Signal): diff --git a/ddtrace/debugging/_signal/snapshot.py b/ddtrace/debugging/_signal/snapshot.py index d9e62b07285..54b23830be5 100644 --- a/ddtrace/debugging/_signal/snapshot.py +++ b/ddtrace/debugging/_signal/snapshot.py @@ -1,4 +1,3 @@ -from collections import ChainMap from dataclasses import dataclass from dataclasses import field from itertools import chain @@ -18,10 +17,7 @@ from ddtrace.debugging._probe.model import FunctionLocationMixin from ddtrace.debugging._probe.model import LineLocationMixin from ddtrace.debugging._probe.model import LiteralTemplateSegment -from ddtrace.debugging._probe.model import LogFunctionProbe -from ddtrace.debugging._probe.model import LogLineProbe from ddtrace.debugging._probe.model import LogProbeMixin -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod from ddtrace.debugging._probe.model import TemplateSegment from ddtrace.debugging._redaction import REDACTED_PLACEHOLDER from ddtrace.debugging._redaction import DDRedactedExpressionError @@ -31,10 +27,8 @@ from ddtrace.debugging._signal import utils from ddtrace.debugging._signal.model import EvaluationError from ddtrace.debugging._signal.model import LogSignal -from ddtrace.debugging._signal.model import SignalState from ddtrace.debugging._signal.utils import serialize from ddtrace.internal.compat import ExcInfoType -from ddtrace.internal.rate_limiter import RateLimitExceeded from ddtrace.internal.utils.time import HourGlass @@ -126,92 +120,25 @@ def _eval_message(self, _locals: Mapping[str, Any]) -> None: probe = cast(LogProbeMixin, self.probe) self._message = "".join([self._eval_segment(s, _locals) for s in probe.segments]) - def enter(self): - if not isinstance(self.probe, LogFunctionProbe): - return - - probe = self.probe + def _do(self, retval, exc_info, scope): + probe = cast(LogProbeMixin, self.probe) frame = self.frame - if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: - return - - scope = ChainMap(self.args, frame.f_globals) - - if not self._eval_condition(scope): - return - - if probe.limiter.limit() is RateLimitExceeded: - self.state = SignalState.SKIP_RATE - return + self._eval_message(scope) - if probe.take_snapshot: - self.entry_capture = _capture_context(frame, (None, None, None), limits=probe.limits) + self._stack = utils.capture_stack(self.frame) - if probe.evaluate_at == ProbeEvaluateTimingForMethod.ENTER: - self._eval_message(scope) - self.state = SignalState.DONE + return _capture_context(frame, exc_info, retval=retval, limits=probe.limits) if probe.take_snapshot else None - def exit(self, retval, exc_info, duration): - if not isinstance(self.probe, LogFunctionProbe): - return - - probe = self.probe - full_scope = self.get_full_scope(retval, exc_info, duration) - - if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: - if not self._eval_condition(full_scope): - return - if probe.limiter.limit() is RateLimitExceeded: - self.state = SignalState.SKIP_RATE - return - elif self.state not in {SignalState.NONE, SignalState.DONE}: - return - - if probe.take_snapshot: - self.return_capture = _capture_context(self.frame, exc_info, retval=retval, limits=probe.limits) + def enter(self, scope: Mapping[str, Any]) -> None: + self.entry_capture = self._do(_NOTSET, (None, None, None), scope) + def exit(self, retval, exc_info, duration, scope) -> None: self.duration = duration - self.state = SignalState.DONE - if probe.evaluate_at != ProbeEvaluateTimingForMethod.ENTER: - self._eval_message(full_scope) - - stack = utils.capture_stack(self.frame) - - # Fix the line number of the top frame. This might have been mangled by - # the instrumented exception handling of function probes. - tb = exc_info[2] - while tb is not None: - frame = tb.tb_frame - if frame == self.frame: - stack[0]["lineNumber"] = tb.tb_lineno - break - tb = tb.tb_next - - self._stack = stack - - def line(self): - if not isinstance(self.probe, LogLineProbe): - return - - frame = self.frame - probe = self.probe - - if not self._eval_condition(frame.f_locals): - return - - if probe.take_snapshot: - if probe.limiter.limit() is RateLimitExceeded: - self.state = SignalState.SKIP_RATE - return - - self.line_capture = _capture_context(frame, sys.exc_info(), limits=probe.limits) - - self._eval_message(ChainMap(frame.f_locals, frame.f_globals)) - - self._stack = utils.capture_stack(frame) + self.return_capture = self._do(retval, exc_info, scope) - self.state = SignalState.DONE + def line(self, scope) -> None: + self.line_capture = self._do(_NOTSET, sys.exc_info(), scope) @property def message(self) -> Optional[str]: diff --git a/ddtrace/debugging/_signal/tracing.py b/ddtrace/debugging/_signal/tracing.py index 226e0230d90..9d3712a963b 100644 --- a/ddtrace/debugging/_signal/tracing.py +++ b/ddtrace/debugging/_signal/tracing.py @@ -7,16 +7,12 @@ from ddtrace.constants import ORIGIN_KEY from ddtrace.debugging._expressions import DDExpressionEvaluationError from ddtrace.debugging._probe.model import Probe -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod -from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe -from ddtrace.debugging._probe.model import SpanDecorationLineProbe from ddtrace.debugging._probe.model import SpanDecorationMixin from ddtrace.debugging._probe.model import SpanDecorationTargetSpan from ddtrace.debugging._probe.model import SpanFunctionProbe from ddtrace.debugging._signal.model import EvaluationError from ddtrace.debugging._signal.model import LogSignal from ddtrace.debugging._signal.model import Signal -from ddtrace.debugging._signal.model import SignalState from ddtrace.debugging._signal.utils import serialize from ddtrace.internal.compat import ExcInfoType from ddtrace.internal.logger import get_logger @@ -36,16 +32,12 @@ class DynamicSpan(Signal): _span_cm: t.Optional[Span] = field(init=False, default=None) def __post_init__(self) -> None: - self._span_cm = None + super().__post_init__() - def enter(self) -> None: - probe = self.probe - if not isinstance(probe, SpanFunctionProbe): - log.debug("Dynamic span entered with non-span probe: %s", self.probe) - return + self._span_cm = None - if not self._eval_condition(self.args): - return + def enter(self, scope: t.Mapping[str, t.Any]) -> None: + probe = t.cast(SpanFunctionProbe, self.probe) self._span_cm = ddtrace.tracer.trace( SPAN_NAME, @@ -59,18 +51,12 @@ def enter(self) -> None: span.set_tag_str(PROBE_ID_TAG_NAME, probe.probe_id) span.set_tag_str(ORIGIN_KEY, "di") - self.state = SignalState.DONE - - def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float) -> None: - if not isinstance(self.probe, SpanFunctionProbe): - log.debug("Dynamic span exited with non-span probe: %s", self.probe) - return - + def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float, scope: t.Mapping[str, t.Any]) -> None: if self._span_cm is not None: # Condition evaluated to true so we created a span. Finish it. self._span_cm.__exit__(*exc_info) - def line(self): + def line(self, scope): raise NotImplementedError("Dynamic line spans are not supported in Python") @@ -111,36 +97,14 @@ def _decorate_span(self, scope: t.Mapping[str, t.Any]) -> None: span.set_tag_str(tag.name, tag_value if _isinstance(tag_value, str) else serialize(tag_value)) span.set_tag_str("_dd.di.%s.probe_id" % tag.name, t.cast(Probe, probe).probe_id) - def enter(self) -> None: - probe = self.probe - if not isinstance(probe, SpanDecorationFunctionProbe): - log.debug("Span decoration entered with non-span decoration probe: %s", self.probe) - return - - if probe.evaluate_at is ProbeEvaluateTimingForMethod.ENTER: - self._decorate_span(self.args) - self.state = SignalState.DONE - - def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float) -> None: - probe = self.probe - - if not isinstance(probe, SpanDecorationFunctionProbe): - log.debug("Span decoration exited with non-span decoration probe: %s", self.probe) - return - - if probe.evaluate_at is ProbeEvaluateTimingForMethod.EXIT: - self._decorate_span(self.get_full_scope(retval, exc_info, duration)) - self.state = SignalState.DONE - - def line(self): - probe = self.probe - if not isinstance(probe, SpanDecorationLineProbe): - log.debug("Span decoration on line with non-span decoration probe: %s", self.probe) - return + def enter(self, scope: t.Mapping[str, t.Any]) -> None: + self._decorate_span(scope) - self._decorate_span(self.frame.f_locals) + def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float, scope: t.Mapping[str, t.Any]) -> None: + self._decorate_span(scope) - self.state = SignalState.DONE + def line(self, scope: t.Mapping[str, t.Any]): + self._decorate_span(scope) @property def message(self): diff --git a/tests/debugging/probe/test_remoteconfig.py b/tests/debugging/probe/test_remoteconfig.py index 662732b5a38..de7a0c35f99 100644 --- a/tests/debugging/probe/test_remoteconfig.py +++ b/tests/debugging/probe/test_remoteconfig.py @@ -156,7 +156,7 @@ def cb(e, ps): di_config.diagnostics_interval = 0.5 try: adapter = SyncProbeRCAdapter(None, cb) - # Wait to allow the next call to the adapter to generate a status event + remoteconfig_poller.register("TEST", adapter, skip_enabled=True) adapter.append_and_publish( { diff --git a/tests/debugging/signal/test_collector.py b/tests/debugging/signal/test_collector.py index c7b9e752662..2e2a77ec098 100644 --- a/tests/debugging/signal/test_collector.py +++ b/tests/debugging/signal/test_collector.py @@ -5,7 +5,6 @@ import mock -from ddtrace.debugging._probe.model import DDExpression from ddtrace.debugging._signal.collector import SignalCollector from ddtrace.debugging._signal.model import LogSignal from ddtrace.debugging._signal.model import SignalState @@ -21,47 +20,6 @@ def mock_encoder(wraps=None): return encoder, snapshot_encoder -def test_collector_cond(): - encoder, _ = mock_encoder() - - collector = SignalCollector(encoder=encoder) - - def foo(a=42): - c = True # noqa - snapshot = Snapshot( - probe=create_snapshot_line_probe( - probe_id=uuid4(), - source_file="file.py", - line=123, - condition=DDExpression("a not null", lambda _: _["a"] is not None), - ), - frame=sys._getframe(), - thread=threading.current_thread(), - ) - snapshot.line() - collector.push(snapshot) - - foo() - - def bar(b=None): - snapshot = Snapshot( - probe=create_snapshot_line_probe( - probe_id=uuid4(), - source_file="file.py", - line=123, - condition=DDExpression("b not null", lambda _: _["b"] is not None), - ), - frame=sys._getframe(), - thread=threading.current_thread(), - ) - snapshot.line() - collector.push(snapshot) - - bar() - - encoder.put.assert_called_once() - - def test_collector_collect_enqueue_only_commit_state(): class MockLogSignal(LogSignal): def __init__(self, *args, **kwargs): @@ -69,13 +27,13 @@ def __init__(self, *args, **kwargs): self.exit_call_count = 0 self.enter_call_count = 0 - def enter(self): + def enter(self, scope): self.enter_call_count += 1 - def exit(self, retval, exc_info, duration): + def exit(self, retval, exc_info, duration, scope): self.exit_call_count += 1 - def line(self): + def line(self, scope): return @property @@ -85,30 +43,38 @@ def message(self): def has_message(self): return True - encoder, _ = mock_encoder() - - collector = SignalCollector(encoder=encoder) + c = 0 for i in range(10): - mocked_signal = MockLogSignal(mock.Mock(), None, None) - with collector.attach(mocked_signal): - assert mocked_signal.enter_call_count == 1 - mocked_signal.state = SignalState.DONE if i % 2 == 0 else SignalState.SKIP_COND - assert mocked_signal.exit_call_count == 1 + mocked_signal = MockLogSignal(mock.Mock(), sys._getframe(), threading.current_thread()) + mocked_signal.do_enter() + + assert mocked_signal.enter_call_count == 1 + done = 1 - (i % 2) + mocked_signal.state = SignalState.DONE if done else SignalState.SKIP_COND - assert len(encoder.put.mock_calls) == 5 + mocked_signal.do_exit(None, sys.exc_info(), 0) + + assert mocked_signal.exit_call_count == 0 + c += done + + assert c == 5 def test_collector_push_enqueue(): encoder, _ = mock_encoder() collector = SignalCollector(encoder=encoder) + frame = inspect.currentframe() + assert frame is not None + for _ in range(10): snapshot = Snapshot( probe=create_snapshot_line_probe(probe_id=uuid4(), source_file="file.py", line=123), - frame=inspect.currentframe(), + frame=frame, thread=threading.current_thread(), ) - snapshot.line() + snapshot.line({}) + snapshot.state = SignalState.DONE collector.push(snapshot) assert len(encoder.put.mock_calls) == 10 diff --git a/tests/debugging/signal/test_model.py b/tests/debugging/signal/test_model.py index d99500e093f..b22b3bbb408 100644 --- a/tests/debugging/signal/test_model.py +++ b/tests/debugging/signal/test_model.py @@ -1,36 +1,28 @@ import sys from threading import current_thread -from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.debugging._signal.model import Signal from tests.debugging.utils import create_log_function_probe -def test_enriched_args_locals_globals(): - duration = 123456 - full_scope = dict( - Snapshot( - probe=create_log_function_probe( - probe_id="test_duration_millis", - module="foo", - func_qname="bar", - template="", - segments=[], - ), - frame=sys._getframe(), - thread=current_thread(), - ).get_full_scope(None, (None, None, None), duration) - ) +class MockSignal(Signal): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.scope = {} - # Check for globals - assert "__file__" in full_scope + def enter(self, scope): + self.scope = scope - # Check for locals - assert "duration" in full_scope + def exit(self, retval, exc_info, duration, scope): + self.scope = scope + + def line(self, scope): + self.scope = scope -def test_duration_millis(): +def test_enriched_args_locals_globals(): duration = 123456 - full_scope = Snapshot( + signal = MockSignal( probe=create_log_function_probe( probe_id="test_duration_millis", module="foo", @@ -40,6 +32,15 @@ def test_duration_millis(): ), frame=sys._getframe(), thread=current_thread(), - ).get_full_scope(None, (None, None, None), duration) + ) + signal.do_exit(None, (None, None, None), duration) + exit_scope = signal.scope + + # Check for globals + assert "__file__" in exit_scope + + # Check for locals + assert "duration" in exit_scope - assert full_scope["@duration"] == duration / 1e6 + # Check for the correct duration units + assert exit_scope["@duration"] == duration / 1e6 diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index 4d453b888ce..a59824741b6 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -12,7 +12,7 @@ from ddtrace.debugging._debugger import DebuggerWrappingContext from ddtrace.debugging._probe.model import DDExpression from ddtrace.debugging._probe.model import MetricProbeKind -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import ProbeEvalTiming from ddtrace.debugging._probe.model import SpanDecoration from ddtrace.debugging._probe.model import SpanDecorationTag from ddtrace.debugging._probe.model import SpanDecorationTargetSpan @@ -20,6 +20,7 @@ from ddtrace.debugging._redaction import REDACTED_PLACEHOLDER as REDACTED from ddtrace.debugging._redaction import dd_compile_redacted as dd_compile from ddtrace.debugging._signal.model import SignalState +from ddtrace.debugging._signal.snapshot import _EMPTY_CAPTURED_CONTEXT from ddtrace.debugging._signal.tracing import SPAN_NAME from ddtrace.debugging._signal.utils import redacted_value from ddtrace.internal.remoteconfig.worker import remoteconfig_poller @@ -181,10 +182,7 @@ def test_debugger_function_probe_on_instance_method(): assert snapshot_data["stack"][0]["fileName"].endswith("stuff.py") assert snapshot_data["stack"][0]["function"] == "instancestuff" - entry_capture = snapshot_data["captures"]["entry"] - assert set(entry_capture["arguments"].keys()) == {"self", "bar"} - assert entry_capture["locals"] == {} - assert entry_capture["throwable"] is None + assert snapshot_data["captures"]["entry"] == _EMPTY_CAPTURED_CONTEXT return_capture = snapshot_data["captures"]["return"] assert set(return_capture["arguments"].keys()) == {"self", "bar"} @@ -776,7 +774,7 @@ def test_debugger_condition_eval_error_get_reported_once(): assert "'foo'" == evaluationErrors[0]["message"] -def test_debugger_function_probe_eval_on_enter(): +def test_debugger_function_probe_eval_on_entry(): from tests.submod.stuff import mutator with debugger() as d: @@ -785,7 +783,7 @@ def test_debugger_function_probe_eval_on_enter(): probe_id="enter-probe", module="tests.submod.stuff", func_qname="mutator", - evaluate_at=ProbeEvaluateTimingForMethod.ENTER, + evaluate_at=ProbeEvalTiming.ENTRY, condition=DDExpression( dsl="not(contains(arg,42))", callable=dd_compile({"not": {"contains": [{"ref": "arg"}, 42]}}) ), @@ -824,7 +822,7 @@ def test_debugger_function_probe_eval_on_exit(): probe_id="exit-probe", module="tests.submod.stuff", func_qname="mutator", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, condition=DDExpression(dsl="contains(arg,42)", callable=dd_compile({"contains": [{"ref": "arg"}, 42]})), ) ) @@ -873,7 +871,7 @@ def __init__(self, age, name): assert snapshot, d.test_queue -def test_debugger_log_live_probe_generate_messages(): +def test_debugger_log_line_probe_generate_messages(): from tests.submod.stuff import Stuff with debugger(upload_flush_interval=float("inf")) as d: @@ -1027,7 +1025,7 @@ def test_debugger_function_probe_ordering(self): probe_id="span-decoration", module="tests.submod.stuff", func_qname="mutator", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, target_span=SpanDecorationTargetSpan.ACTIVE, decorations=[ SpanDecoration( @@ -1142,7 +1140,7 @@ def test_debugger_redacted_identifiers(): probe_id="function-probe", module="tests.submod.stuff", func_qname="sensitive_stuff", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, ), ) @@ -1222,7 +1220,7 @@ def test_debugger_exception_conditional_function_probe(): probe_id="probe-instance-method", module="tests.submod.stuff", func_qname="throwexcstuff", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, condition=DDExpression( dsl="expr.__class__.__name__ == 'Exception'", callable=dd_compile( diff --git a/tests/debugging/test_debugger_span_decoration.py b/tests/debugging/test_debugger_span_decoration.py index fe7cbf2702c..fd6e8d2aca7 100644 --- a/tests/debugging/test_debugger_span_decoration.py +++ b/tests/debugging/test_debugger_span_decoration.py @@ -2,7 +2,7 @@ import sys import ddtrace -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import ProbeEvalTiming from ddtrace.debugging._probe.model import SpanDecoration from ddtrace.debugging._probe.model import SpanDecorationTag from ddtrace.debugging._probe.model import SpanDecorationTargetSpan @@ -41,7 +41,7 @@ def test_debugger_span_decoration_probe_on_inner_function_active_span(self): probe_id="span-decoration", module="tests.submod.traced_stuff", func_qname="inner", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, target_span=SpanDecorationTargetSpan.ACTIVE, decorations=[ SpanDecoration( @@ -77,7 +77,7 @@ def test_debugger_span_decoration_probe_on_inner_function_active_span_unconditio probe_id="span-decoration", module="tests.submod.traced_stuff", func_qname="inner", - evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + evaluate_at=ProbeEvalTiming.EXIT, target_span=SpanDecorationTargetSpan.ACTIVE, decorations=[ SpanDecoration( @@ -149,7 +149,7 @@ def test_debugger_span_decoration_probe_on_traced_function_active_span(self): probe_id="span-decoration", module="tests.submod.traced_stuff", func_qname="traceme", - evaluate_at=ProbeEvaluateTimingForMethod.ENTER, + evaluate_at=ProbeEvalTiming.ENTRY, target_span=SpanDecorationTargetSpan.ACTIVE, decorations=[ SpanDecoration( diff --git a/tests/debugging/test_encoding.py b/tests/debugging/test_encoding.py index 81239c639da..c06e5000ed8 100644 --- a/tests/debugging/test_encoding.py +++ b/tests/debugging/test_encoding.py @@ -210,7 +210,7 @@ def test_batch_json_encoder(): buffer_size = 30 * (1 << 20) queue = SignalQueue(encoder=LogSignalJsonEncoder(None), buffer_size=buffer_size) - s.line() + s.line({}) snapshot_size = queue.put(s) @@ -241,7 +241,7 @@ def test_batch_flush_reencode(): thread=threading.current_thread(), ) - s.line() + s.line({}) queue = SignalQueue(LogSignalJsonEncoder(None)) diff --git a/tests/debugging/utils.py b/tests/debugging/utils.py index 82bac1b5170..d4828be3cfd 100644 --- a/tests/debugging/utils.py +++ b/tests/debugging/utils.py @@ -10,7 +10,7 @@ from ddtrace.debugging._probe.model import LogLineProbe from ddtrace.debugging._probe.model import MetricFunctionProbe from ddtrace.debugging._probe.model import MetricLineProbe -from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import ProbeEvalTiming from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe from ddtrace.debugging._probe.model import SpanDecorationLineProbe from ddtrace.debugging._probe.model import SpanDecorationTargetSpan @@ -60,9 +60,9 @@ def _wrapper(*args, **kwargs): return _wrapper -def function_location_defaults(f): +def timing_defaults(f): def _wrapper(*args, **kwargs): - kwargs.setdefault("evaluate_at", ProbeEvaluateTimingForMethod.DEFAULT) + kwargs.setdefault("evaluate_at", ProbeEvalTiming.DEFAULT) return f(*args, **kwargs) return _wrapper @@ -123,7 +123,7 @@ def create_snapshot_line_probe(**kwargs): @create_probe_defaults @probe_conditional_defaults -@function_location_defaults +@timing_defaults @snapshot_probe_defaults def create_snapshot_function_probe(**kwargs): return LogFunctionProbe(**kwargs) @@ -138,7 +138,7 @@ def create_log_line_probe(**kwargs): @create_probe_defaults @probe_conditional_defaults -@function_location_defaults +@timing_defaults @log_probe_defaults def create_log_function_probe(**kwargs): return LogFunctionProbe(**kwargs) @@ -153,7 +153,7 @@ def create_metric_line_probe(**kwargs): @create_probe_defaults @probe_conditional_defaults -@function_location_defaults +@timing_defaults @metric_probe_defaults def create_metric_function_probe(**kwargs): return MetricFunctionProbe(**kwargs) @@ -161,7 +161,6 @@ def create_metric_function_probe(**kwargs): @create_probe_defaults @probe_conditional_defaults -@function_location_defaults @span_probe_defaults def create_span_function_probe(**kwargs): return SpanFunctionProbe(**kwargs) @@ -174,7 +173,7 @@ def create_span_decoration_line_probe(**kwargs): @create_probe_defaults -@function_location_defaults +@timing_defaults @span_decoration_probe_defaults def create_span_decoration_function_probe(**kwargs): return SpanDecorationFunctionProbe(**kwargs) From 105018feb6f3a6c049535c8ff2241229ede2be7d Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:47:27 +0100 Subject: [PATCH 075/372] chore(asm): refactor for waf telemetry logs (#11181) - refactor the code for telemetry logs for the waf. - add error logs for waf run. - improve messages following RFC "In-APP WAF Error Telemetry RFC" - update tests accordingly - improve code to handle internal errors of libddwaf - add test for telemetry log on waf internal errors using mock (we don't have a way to force libddwaf to generate an internal error) APPSEC-55199 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_ddwaf/__init__.py | 19 ++++++++-- ddtrace/appsec/_metrics.py | 21 +++++------ ddtrace/appsec/_processor.py | 23 ++---------- tests/appsec/appsec/test_telemetry.py | 48 ++++++++++++++++++++----- tests/appsec/contrib_appsec/conftest.py | 21 ++++------- 5 files changed, 72 insertions(+), 60 deletions(-) diff --git a/ddtrace/appsec/_ddwaf/__init__.py b/ddtrace/appsec/_ddwaf/__init__.py index 478cf598231..f04f7c79853 100644 --- a/ddtrace/appsec/_ddwaf/__init__.py +++ b/ddtrace/appsec/_ddwaf/__init__.py @@ -102,6 +102,10 @@ def __init__( obfuscation_parameter_key_regexp: bytes, obfuscation_parameter_value_regexp: bytes, ): + # avoid circular import + from ddtrace.appsec._metrics import _set_waf_error_log + + self.report_error = _set_waf_error_log config = ddwaf_config( key_regex=obfuscation_parameter_key_regexp, value_regex=obfuscation_parameter_value_regexp ) @@ -109,7 +113,7 @@ def __init__( ruleset_map_object = ddwaf_object.create_without_limits(ruleset_map) self._handle = py_ddwaf_init(ruleset_map_object, ctypes.byref(config), ctypes.byref(diagnostics)) self._cached_version = "" - self._set_info(diagnostics) + self._set_info(diagnostics, "init") info = self.info if not self._handle or info.failed: # We keep the handle alive in case of errors, as some valid rules can be loaded @@ -126,12 +130,20 @@ def __init__( def required_data(self) -> List[str]: return py_ddwaf_known_addresses(self._handle) if self._handle else [] - def _set_info(self, diagnostics: ddwaf_object) -> None: + def _set_info(self, diagnostics: ddwaf_object, action: str) -> None: info_struct: dict[str, Any] = diagnostics.struct or {} # type: ignore rules = info_struct.get("rules", {}) errors_result = rules.get("errors", {}) version = info_struct.get("ruleset_version", self._cached_version) self._cached_version = version + for key, value in info_struct.items(): + if isinstance(value, dict): + if value.get("error", False): + self.report_error(f"appsec.waf.error::{action}::{key}::{value['error']}", self._cached_version) + elif value.get("errors", False): + self.report_error( + f"appsec.waf.error::{action}::{key}::{str(value['errors'])}", self._cached_version, False + ) self._info = DDWaf_info( len(rules.get("loaded", [])), len(rules.get("failed", [])), @@ -149,7 +161,7 @@ def update_rules(self, new_rules: Dict[str, DDWafRulesType]) -> bool: rules = ddwaf_object.create_without_limits(new_rules) diagnostics = ddwaf_object() result = py_ddwaf_update(self._handle, rules, diagnostics) - self._set_info(diagnostics) + self._set_info(diagnostics, "update") ddwaf_object_free(rules) if result: LOGGER.debug("DDWAF.update_rules success.\ninfo %s", self.info) @@ -189,6 +201,7 @@ def run( error = ddwaf_run(ctx.ctx, wrapper, wrapper_ephemeral, ctypes.byref(result), int(timeout_ms * 1000)) if error < 0: LOGGER.debug("run DDWAF error: %d\ninput %s\nerror %s", error, wrapper.struct, self.info.errors) + self.report_error(f"appsec.waf.request::error::{error}", self._cached_version) if error == DDWAF_ERR_INTERNAL: # result is not valid return DDWaf_result(error, [], {}, 0, 0, False, 0, {}) diff --git a/ddtrace/appsec/_metrics.py b/ddtrace/appsec/_metrics.py index 776c25f1735..f8713dc5ea7 100644 --- a/ddtrace/appsec/_metrics.py +++ b/ddtrace/appsec/_metrics.py @@ -1,5 +1,4 @@ from ddtrace.appsec import _asm_request_context -from ddtrace.appsec._ddwaf import DDWaf_info from ddtrace.appsec._ddwaf import version as _version from ddtrace.appsec._deduplications import deduplication from ddtrace.internal import telemetry @@ -14,17 +13,15 @@ @deduplication -def _set_waf_error_metric(msg: str, stack_trace: str, info: DDWaf_info) -> None: - try: - tags = { - "waf_version": DDWAF_VERSION, - "lib_language": "python", - } - if info and info.version: - tags["event_rules_version"] = info.version - telemetry.telemetry_writer.add_log(TELEMETRY_LOG_LEVEL.ERROR, msg, stack_trace=stack_trace, tags=tags) - except Exception: - log.warning("Error reporting ASM WAF logs metrics", exc_info=True) +def _set_waf_error_log(msg: str, version: str, error_level: bool = True) -> None: + tags = { + "waf_version": DDWAF_VERSION, + "lib_language": "python", + } + if version: + tags["event_rules_version"] = version + level = TELEMETRY_LOG_LEVEL.ERROR if error_level else TELEMETRY_LOG_LEVEL.WARNING + telemetry.telemetry_writer.add_log(level, msg, tags=tags) def _set_waf_updates_metric(info): diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 239961cb39f..0c242a7cbb2 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -4,7 +4,6 @@ from json.decoder import JSONDecodeError import os import os.path -import traceback from typing import Any from typing import Dict from typing import List @@ -26,7 +25,6 @@ from ddtrace.appsec._constants import WAF_DATA_NAMES from ddtrace.appsec._ddwaf import DDWaf_result from ddtrace.appsec._ddwaf.ddwaf_types import ddwaf_context_capsule -from ddtrace.appsec._metrics import _set_waf_error_metric from ddtrace.appsec._metrics import _set_waf_init_metric from ddtrace.appsec._metrics import _set_waf_request_metrics from ddtrace.appsec._metrics import _set_waf_updates_metric @@ -173,14 +171,6 @@ def __post_init__(self) -> None: self._ddwaf = DDWaf( self._rules, self.obfuscation_parameter_key_regexp, self.obfuscation_parameter_value_regexp ) - if not self._ddwaf._handle or self._ddwaf.info.failed: - stack_trace = "DDWAF.__init__: invalid rules\n ruleset: %s\nloaded:%s\nerrors:%s\n" % ( - self._rules, - self._ddwaf.info.loaded, - self._ddwaf.info.errors, - ) - _set_waf_error_metric("WAF init error. Invalid rules", stack_trace, self._ddwaf.info) - _set_waf_init_metric(self._ddwaf.info) except ValueError: # Partial of DDAS-0005-00 @@ -201,17 +191,8 @@ def _update_rules(self, new_rules: Dict[str, Any]) -> bool: result = False if asm_config._asm_static_rule_file is not None: return result - try: - result = self._ddwaf.update_rules(new_rules) - _set_waf_updates_metric(self._ddwaf.info) - except TypeError: - error_msg = "Error updating ASM rules. TypeError exception " - log.debug(error_msg, exc_info=True) - _set_waf_error_metric(error_msg, traceback.format_exc(), self._ddwaf.info) - if not result: - error_msg = "Error updating ASM rules. Invalid rules" - log.debug(error_msg) - _set_waf_error_metric(error_msg, "", self._ddwaf.info) + result = self._ddwaf.update_rules(new_rules) + _set_waf_updates_metric(self._ddwaf.info) self._update_required() return result diff --git a/tests/appsec/appsec/test_telemetry.py b/tests/appsec/appsec/test_telemetry.py index bf260b60c9f..8678820e8d6 100644 --- a/tests/appsec/appsec/test_telemetry.py +++ b/tests/appsec/appsec/test_telemetry.py @@ -1,9 +1,13 @@ import os from time import sleep +import mock import pytest +from ddtrace import tracer +import ddtrace.appsec._asm_request_context as asm_request_context from ddtrace.appsec._ddwaf import version +import ddtrace.appsec._ddwaf.ddwaf_types from ddtrace.appsec._deduplications import deduplication from ddtrace.appsec._processor import AppSecSpanProcessor from ddtrace.contrib.trace_utils import set_http_meta @@ -18,6 +22,8 @@ config_asm = {"_asm_enabled": True} config_good_rules = {"_asm_static_rule_file": rules.RULES_GOOD_PATH, "_asm_enabled": True} +invalid_rule_update = {"rules": {"test": "invalid"}} +invalid_error = """appsec.waf.error::update::rules::bad cast, expected 'array', obtained 'map'""" def _assert_generate_metrics(metrics_result, is_rule_triggered=False, is_blocked_request=False): @@ -106,8 +112,10 @@ def test_log_metric_error_ddwaf_init(telemetry_writer): list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 1 - assert list_metrics_logs[0]["message"] == "WAF init error. Invalid rules" - assert list_metrics_logs[0]["stack_trace"].startswith("DDWAF.__init__: invalid rules") + assert ( + list_metrics_logs[0]["message"] == "appsec.waf.error::init::rules::" + """{"missing key 'conditions'": ['crs-913-110'], "missing key 'tags'": ['crs-942-100']}""" + ) assert "waf_version:{}".format(version()) in list_metrics_logs[0]["tags"] @@ -142,22 +150,43 @@ def test_log_metric_error_ddwaf_timeout(telemetry_writer, tracer): def test_log_metric_error_ddwaf_update(telemetry_writer): with override_global_config(dict(_asm_enabled=True, _deduplication_enabled=False)): span_processor = AppSecSpanProcessor() - span_processor._update_rules({}) + span_processor._update_rules(invalid_rule_update) list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 1 - assert list_metrics_logs[0]["message"] == "Error updating ASM rules. Invalid rules" - assert list_metrics_logs[0].get("stack_trace") is None + assert list_metrics_logs[0]["message"] == invalid_error assert "waf_version:{}".format(version()) in list_metrics_logs[0]["tags"] +unpatched_run = ddtrace.appsec._ddwaf.ddwaf_types.ddwaf_run + + +def _wrapped_run(*args, **kwargs): + unpatched_run(*args, **kwargs) + return -3 + + +@mock.patch.object(ddtrace.appsec._ddwaf, "ddwaf_run", new=_wrapped_run) +def test_log_metric_error_ddwaf_internal_error(telemetry_writer): + """Test that an internal error is logged when the WAF returns an internal error.""" + with override_global_config(dict(_asm_enabled=True, _deduplication_enabled=False)): + with tracer.trace("test", span_type=SpanTypes.WEB, service="test") as span: + span_processor = AppSecSpanProcessor() + span_processor.on_span_start(span) + asm_request_context._call_waf(span, {}) + list_metrics_logs = list(telemetry_writer._logs) + assert len(list_metrics_logs) == 1 + assert list_metrics_logs[0]["message"] == "appsec.waf.request::error::-3" + assert "waf_version:{}".format(version()) in list_metrics_logs[0]["tags"] + + def test_log_metric_error_ddwaf_update_deduplication(telemetry_writer): with override_global_config(dict(_asm_enabled=True)): span_processor = AppSecSpanProcessor() - span_processor._update_rules({}) + span_processor._update_rules(invalid_rule_update) telemetry_writer.reset_queues() span_processor = AppSecSpanProcessor() - span_processor._update_rules({}) + span_processor._update_rules(invalid_rule_update) list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 0 @@ -169,13 +198,14 @@ def test_log_metric_error_ddwaf_update_deduplication_timelapse(telemetry_writer) with override_global_config(dict(_asm_enabled=True)): sleep(0.2) span_processor = AppSecSpanProcessor() - span_processor._update_rules({}) + span_processor._update_rules(invalid_rule_update) list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 1 + assert list_metrics_logs[0]["message"] == invalid_error telemetry_writer.reset_queues() sleep(0.2) span_processor = AppSecSpanProcessor() - span_processor._update_rules({}) + span_processor._update_rules(invalid_rule_update) list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 1 finally: diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index 7aa2d1433d6..9773ef124c9 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -4,7 +4,6 @@ # ensure the tracer is loaded and started first for possible iast patching print(f"ddtrace version {ddtrace.version.get_version()}") -import unittest.mock # noqa: E402 import pytest # noqa: E402 @@ -37,20 +36,12 @@ def get_root_span(): @pytest.fixture -def check_waf_timeout(request, printer): - with unittest.mock.patch("ddtrace.appsec._processor._set_waf_error_metric", autospec=True) as mock_metrics: - # change timeout to 50 seconds to avoid flaky timeouts - previous_timeout = asm_config._waf_timeout - asm_config._waf_timeout = 50_000.0 - test_failed = request.session.testsfailed - yield - if request.session.testsfailed > test_failed: - for args in mock_metrics.call_args_list: - args = list(args) - if args[0][0] == "WAF run. Timeout errors": - # report the waf timeout error as an addtionnal test error - pytest.fail(f"WAF timeout detected. WAF info {args[0][2]}") - asm_config._waf_timeout = previous_timeout +def check_waf_timeout(request): + # change timeout to 50 seconds to avoid flaky timeouts + previous_timeout = asm_config._waf_timeout + asm_config._waf_timeout = 50_000.0 + yield + asm_config._waf_timeout = previous_timeout @pytest.fixture From bfa9c302ce147b32abe1fbef30c9b587c22cc399 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:30:33 +0000 Subject: [PATCH 076/372] chore: update gevent latest version to 24.10.3 (#11186) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/189a9da.txt | 10 ++++---- .riot/requirements/1aa652f.txt | 40 ++++++++++++++++-------------- .riot/requirements/1ace55b.txt | 41 +++++++++++++++++-------------- .riot/requirements/1bceb88.txt | 42 ++++++++++++++++--------------- .riot/requirements/4211915.txt | 45 ++++++++++++++++++---------------- .riot/requirements/512bff3.txt | 44 +++++++++++++++++---------------- .riot/requirements/51f5382.txt | 42 ++++++++++++++++--------------- .riot/requirements/c7b5ba5.txt | 44 +++++++++++++++++---------------- .riot/requirements/ddba314.txt | 42 ++++++++++++++++--------------- 9 files changed, 184 insertions(+), 166 deletions(-) diff --git a/.riot/requirements/189a9da.txt b/.riot/requirements/189a9da.txt index 6666ffcbb8a..327d29ca153 100644 --- a/.riot/requirements/189a9da.txt +++ b/.riot/requirements/189a9da.txt @@ -10,10 +10,10 @@ aioitertools==0.11.0 aiosignal==1.3.1 async-timeout==4.0.3 asynctest==0.13.0 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 elastic-transport==8.13.1 elasticsearch==8.14.0 @@ -22,7 +22,7 @@ frozenlist==1.3.3 gevent==20.12.1 greenlet==1.0.0 hypothesis==6.45.0 -idna==3.7 +idna==3.10 importlib-metadata==6.7.0 iniconfig==2.0.0 jmespath==1.0.1 @@ -43,7 +43,7 @@ six==1.16.0 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 yarl==1.9.4 zipp==3.15.0 diff --git a/.riot/requirements/1aa652f.txt b/.riot/requirements/1aa652f.txt index 84e8195eb75..c93d6b8a86f 100644 --- a/.riot/requirements/1aa652f.txt +++ b/.riot/requirements/1aa652f.txt @@ -5,44 +5,46 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1aa652f.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 -frozenlist==1.4.1 +frozenlist==1.5.0 gevent==22.10.2 -greenlet==3.0.3 +greenlet==3.1.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.16.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/1ace55b.txt b/.riot/requirements/1ace55b.txt index f0565c55e90..60ffb476378 100644 --- a/.riot/requirements/1ace55b.txt +++ b/.riot/requirements/1ace55b.txt @@ -5,47 +5,50 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1ace55b.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 async-timeout==4.0.3 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 exceptiongroup==1.2.2 -frozenlist==1.4.1 +frozenlist==1.5.0 gevent==21.12.0 greenlet==1.1.3.post0 hypothesis==6.45.0 -idna==3.7 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.19 +tomli==2.0.2 +typing-extensions==4.12.2 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.16.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/1bceb88.txt b/.riot/requirements/1bceb88.txt index 35b07e5cd3d..444e5a3b49c 100644 --- a/.riot/requirements/1bceb88.txt +++ b/.riot/requirements/1bceb88.txt @@ -5,35 +5,37 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1bceb88.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 async-timeout==4.0.3 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.1 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 exceptiongroup==1.2.2 -frozenlist==1.4.1 +frozenlist==1.5.0 gevent==20.12.1 greenlet==1.0.0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 @@ -41,14 +43,14 @@ python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 -zipp==3.19.2 +yarl==1.15.2 +zipp==3.20.2 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/4211915.txt b/.riot/requirements/4211915.txt index 67fac5f1daf..74a4e1c120e 100644 --- a/.riot/requirements/4211915.txt +++ b/.riot/requirements/4211915.txt @@ -5,47 +5,50 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/4211915.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 async-timeout==4.0.3 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 exceptiongroup==1.2.2 -frozenlist==1.4.1 -gevent==24.2.1 -greenlet==3.0.3 +frozenlist==1.5.0 +gevent==24.10.3 +greenlet==3.1.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.19 +tomli==2.0.2 +typing-extensions==4.12.2 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.16.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/512bff3.txt b/.riot/requirements/512bff3.txt index 84360fc5099..044d35664f9 100644 --- a/.riot/requirements/512bff3.txt +++ b/.riot/requirements/512bff3.txt @@ -5,50 +5,52 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/512bff3.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 async-timeout==4.0.3 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 exceptiongroup==1.2.2 -frozenlist==1.4.1 +frozenlist==1.5.0 gevent==21.1.2 greenlet==1.1.3.post0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 -zipp==3.19.2 +yarl==1.16.0 +zipp==3.20.2 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/51f5382.txt b/.riot/requirements/51f5382.txt index c835e5c9312..b483c0f1fb2 100644 --- a/.riot/requirements/51f5382.txt +++ b/.riot/requirements/51f5382.txt @@ -5,44 +5,46 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/51f5382.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 -frozenlist==1.4.1 -gevent==24.2.1 -greenlet==3.0.3 +frozenlist==1.5.0 +gevent==24.10.3 +greenlet==3.1.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.16.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/c7b5ba5.txt b/.riot/requirements/c7b5ba5.txt index 5d3f39b4c62..b600cea7664 100644 --- a/.riot/requirements/c7b5ba5.txt +++ b/.riot/requirements/c7b5ba5.txt @@ -5,50 +5,52 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/c7b5ba5.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 async-timeout==4.0.3 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 exceptiongroup==1.2.2 -frozenlist==1.4.1 +frozenlist==1.5.0 gevent==22.10.1 greenlet==1.1.3.post0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 -zipp==3.19.2 +yarl==1.16.0 +zipp==3.20.2 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 diff --git a/.riot/requirements/ddba314.txt b/.riot/requirements/ddba314.txt index 150f05f70c2..e99a4ed6a0f 100644 --- a/.riot/requirements/ddba314.txt +++ b/.riot/requirements/ddba314.txt @@ -5,44 +5,46 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/ddba314.in # aiobotocore==2.3.1 -aiohttp==3.9.5 -aioitertools==0.11.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aioitertools==0.12.0 aiosignal==1.3.1 -attrs==23.2.0 +attrs==24.2.0 botocore==1.24.21 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -elastic-transport==8.13.1 -elasticsearch==8.14.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 +elastic-transport==8.15.1 +elasticsearch==8.15.1 events==0.5 -frozenlist==1.4.1 -gevent==24.2.1 -greenlet==3.0.3 +frozenlist==1.5.0 +gevent==24.10.3 +greenlet==3.1.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 mock==5.1.0 -multidict==6.0.5 -opensearch-py==2.6.0 +multidict==6.1.0 +opensearch-py==2.7.1 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 +propcache==0.2.0 pynamodb==5.5.1 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 sortedcontainers==2.4.0 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.16.0 -yarl==1.9.4 +yarl==1.16.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.2.0 From 37b12282852df588ba77b2b0557aa12e38937e9f Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:57:21 +0000 Subject: [PATCH 077/372] chore(ci_visibility): fix inadequate pytest test and fix broken git check (#11201) This makes sure that `CIVisibilityGitClient.upload_git_metadata()` does not cause a crash if the `git` binary is absent (which could happen after #11175). It also fixes the `test_pytest_without_git_does_not_crash` test that allowed #11175 to slip through because, since the migration to GitLab, the overall environment in which tests ran is no longer in agentless mode, which meant `_should_upload_git_metadata()` was false, and effectively wasn't exercising `pytest` "without `git`". There is no release note here since #11175 has not been released yet. The backports for that PR will be updated to cherry-pick this commit, so this PR is also not backported. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/git_client.py | 2 +- tests/contrib/pytest/test_pytest.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ddtrace/internal/ci_visibility/git_client.py b/ddtrace/internal/ci_visibility/git_client.py index 6ca8a8ef20c..6152fa12679 100644 --- a/ddtrace/internal/ci_visibility/git_client.py +++ b/ddtrace/internal/ci_visibility/git_client.py @@ -108,7 +108,7 @@ def _get_git_dir(self, cwd=None): # type: (Optional[str]) -> Optional[str] try: return extract_workspace_path(cwd=cwd) - except ValueError: + except (FileNotFoundError, ValueError): return None def upload_git_metadata(self, cwd=None): diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index c10a6cb311a..796dd7422e2 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -4086,7 +4086,11 @@ def test_add_two_number_list(): ) self.testdir.chdir() - with mock.patch("ddtrace.ext.git._get_executable_path", return_value=None): + with mock.patch("ddtrace.ext.git._get_executable_path", return_value=None), mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddconfig", + _get_default_civisibility_ddconfig(), + ) as mock_ddconfig: + mock_ddconfig._ci_visibility_agentless_enabled = True self.inline_run("--ddtrace") spans = self.pop_spans() From ae34e92ce1685712b92e5ce262a5e6e67b581b27 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Tue, 29 Oct 2024 12:05:29 +0100 Subject: [PATCH 078/372] chore(iast): make debug propagation tool work in propagation on tests (#11180) --- tests/appsec/appsec_utils.py | 8 ++++++-- tests/appsec/iast_packages/test_packages.py | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index 1ec13f29a76..b7fd5d4743a 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -54,6 +54,7 @@ def flask_server( env=None, port=8000, assert_debug=False, + manual_propagation_debug=False, ): cmd = [python_cmd, app, "--no-reload"] yield from appsec_application_server( @@ -67,6 +68,7 @@ def flask_server( env=env, port=port, assert_debug=assert_debug, + manual_propagation_debug=manual_propagation_debug, ) @@ -81,6 +83,7 @@ def appsec_application_server( env=None, port=8000, assert_debug=False, + manual_propagation_debug=False, ): env = _build_env(env, file_path=FILE_PATH) env["DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS"] = "0.5" @@ -113,8 +116,9 @@ def appsec_application_server( "stderr": sys.stderr, } if assert_debug: - subprocess_kwargs["stdout"] = subprocess.PIPE - subprocess_kwargs["stderr"] = subprocess.PIPE + if not manual_propagation_debug: + subprocess_kwargs["stdout"] = subprocess.PIPE + subprocess_kwargs["stderr"] = subprocess.PIPE subprocess_kwargs["text"] = True server_process = subprocess.Popen(cmd, **subprocess_kwargs) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 77b7f6f6b4e..5d6ba9ad54e 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -983,7 +983,13 @@ def test_flask_packages_propagation(package, venv, printer): package.install(venv) with flask_server( - python_cmd=venv, iast_enabled="true", remote_configuration_enabled="false", token=None, port=_TEST_PORT + python_cmd=venv, + iast_enabled="true", + remote_configuration_enabled="false", + token=None, + port=_TEST_PORT, + # assert_debug=True, # DEV: uncomment to debug propagation + # manual_propagation=True, # DEV: uncomment to debug propagation ) as context: _, client, pid = context response = client.get(package.url_propagation) From 64b33747062c585df9f0eb810c28d367379a9998 Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Tue, 29 Oct 2024 12:37:40 -0400 Subject: [PATCH 079/372] fix(profiling): fix data race when accessing span for thread (#11167) The ThreadSpanLinks singleton holds the active span (if one exists) for a given thread ID. The `get_active_span_from_thread_id` member function returns a pointer to the active span for a thread. The `link_span` member function sets the active span for a thread. `get_active_span_from_thread_id` accesses the map of spans under a mutex, but returns the pointer after releasing the mutex, meaning `link_span` can modify the members of the Span while the caller of `get_active_span_from_thread_id` is reading them. Fix this by returning a copy of the `Span`. Use a `std::optional` to wrap the return value of `get_active_span_from_thread_id`, rather than returning a pointer. We want to tell whether or not there actually was a span associated with the thread, but returning a pointer would require us to heap allocate the copy of the Span. I added a simplistic regression test which fails reliably without this fix when built with the thread sanitizer enabled. Output like: ``` WARNING: ThreadSanitizer: data race (pid=2971510) Read of size 8 at 0x7b2000004080 by thread T2: #0 memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:823 (libtsan.so.0+0x42313) #1 memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:815 (libtsan.so.0+0x42313) #2 std::__cxx11::basic_string, std::allocator >::_M_assign(std::__cxx11::basic_string, std::allocator > const&) (libstdc++.so.6+0x1432b4) #3 std::__cxx11::basic_string, std::allocator > std::__invoke_impl, std::allocator >, std::__cxx11::basic_string, std::allocator > (*)()>(std::__invoke_other, std::__cxx11::basic_string, std::allocator > (*&&)()) (thread_span_links+0xe46e) #4 std::__invoke_result, std::allocator > (*)()>::type std::__invoke, std::allocator > (*)()>(std::__cxx11::basic_string, std::allocator > (*&&)()) (thread_span_links+0xe2fe) #5 std::__cxx11::basic_string, std::allocator > std::thread::_Invoker, std::allocator > (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (thread_span_links+0xe1cf) #6 std::thread::_Invoker, std::allocator > (*)()> >::operator()() (thread_span_links+0xe0f6) #7 std::thread::_State_impl, std::allocator > (*)()> > >::_M_run() (thread_span_links+0xdf40) #8 (libstdc++.so.6+0xd6df3) Previous write of size 8 at 0x7b2000004080 by thread T1 (mutexes: write M47): #0 memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:823 (libtsan.so.0+0x42313) #1 memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:815 (libtsan.so.0+0x42313) #2 std::__cxx11::basic_string, std::allocator >::_M_assign(std::__cxx11::basic_string, std::allocato r > const&) (libstdc++.so.6+0x1432b4) #3 get() (thread_span_links+0xb570) #4 void std::__invoke_impl(std::__invoke_other, void (*&&)()) (thread_span_links+0xe525) #5 std::__invoke_result::type std::__invoke(void (*&&)()) (thread_span_links+0xe3b5) #6 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (thread_span_links+0xe242) #7 std::thread::_Invoker >::operator()() (thread_span_links+0xe158) [ ... etc ... ] ``` --- .../datadog/profiling/stack_v2/CMakeLists.txt | 6 +++ .../stack_v2/include/thread_span_links.hpp | 3 +- .../profiling/stack_v2/src/stack_renderer.cpp | 4 +- .../stack_v2/src/thread_span_links.cpp | 11 +++-- .../profiling/stack_v2/test/CMakeLists.txt | 25 ++++++++++ .../stack_v2/test/thread_span_links.cpp | 48 +++++++++++++++++++ ...x-data-race-segfault-b14f59c06c6bf9ed.yaml | 5 ++ 7 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt create mode 100644 ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp create mode 100644 releasenotes/notes/profiling-fix-data-race-segfault-b14f59c06c6bf9ed.yaml diff --git a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt index 6c330c9c970..19f3955dab0 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt @@ -117,3 +117,9 @@ if(LIB_INSTALL_DIR) ARCHIVE DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${LIB_INSTALL_DIR}) endif() + +if(BUILD_TESTING) + enable_testing() + add_subdirectory(test) +endif() + diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp index 5f2f69e39ab..1d61f76fe46 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,7 @@ class ThreadSpanLinks ThreadSpanLinks& operator=(ThreadSpanLinks const&) = delete; void link_span(uint64_t thread_id, uint64_t span_id, uint64_t local_root_span_id, std::string span_type); - const Span* get_active_span_from_thread_id(uint64_t thread_id); + const std::optional get_active_span_from_thread_id(uint64_t thread_id); void reset(); static void postfork_child(); diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp index 8abc37ee55a..9a07da3aac2 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp @@ -55,8 +55,8 @@ StackRenderer::render_thread_begin(PyThreadState* tstate, ddup_push_threadinfo(sample, static_cast(thread_id), static_cast(native_id), name); ddup_push_walltime(sample, thread_state.wall_time_ns, 1); - const Span* active_span = ThreadSpanLinks::get_instance().get_active_span_from_thread_id(thread_id); - if (active_span != nullptr) { + const std::optional active_span = ThreadSpanLinks::get_instance().get_active_span_from_thread_id(thread_id); + if (active_span) { ddup_push_span_id(sample, active_span->span_id); ddup_push_local_root_span_id(sample, active_span->local_root_span_id); ddup_push_trace_type(sample, std::string_view(active_span->span_type)); diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp index 602aad9fdb3..80c5ef06bc1 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -19,15 +20,17 @@ ThreadSpanLinks::link_span(uint64_t thread_id, uint64_t span_id, uint64_t local_ thread_id_to_span[thread_id]->span_type = span_type; } -const Span* +const std::optional ThreadSpanLinks::get_active_span_from_thread_id(uint64_t thread_id) { std::lock_guard lock(mtx); - if (thread_id_to_span.find(thread_id) == thread_id_to_span.end()) { - return nullptr; + std::optional span; + auto it = thread_id_to_span.find(thread_id); + if (it != thread_id_to_span.end()) { + span = *(it->second); } - return thread_id_to_span[thread_id].get(); + return span; } void diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt new file mode 100644 index 00000000000..23fcda3eedb --- /dev/null +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -0,0 +1,25 @@ +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.11.0) +set(gtest_force_shared_crt + ON + CACHE BOOL "" FORCE) +set(INSTALL_GTEST + OFF + CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +include(GoogleTest) +include(AnalysisFunc) + +function(dd_wrapper_add_test name) + add_executable(${name} ${ARGN}) + target_include_directories(${name} PRIVATE ../include) + target_link_libraries(${name} PRIVATE gmock gtest_main _stack_v2) + add_ddup_config(${name}) + + gtest_discover_tests(${name}) +endfunction() + +# Add the tests +dd_wrapper_add_test(thread_span_links thread_span_links.cpp) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp new file mode 100644 index 00000000000..b668c3b3d80 --- /dev/null +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include "thread_span_links.hpp" + +static void +get() +{ + for (int i = 0; i < 100; i++) { + std::string span_type; + for (int j = 0; j < i; j++) { + span_type.append("a"); + } + Datadog::ThreadSpanLinks::get_instance().link_span(42, 1, 2, span_type); + } +} + +static std::string +set() +{ + std::string s; + for (int i = 0; i < 100; i++) { + auto thing = Datadog::ThreadSpanLinks::get_instance().get_active_span_from_thread_id(42); + if (!thing) { + continue; + } + s = thing->span_type; + } + return s; +} + +TEST(ThreadSpanLinksConcurrency, GetSetRace) +{ + std::thread t1(get); + std::thread t2(set); + t1.join(); + t2.join(); +} + +int +main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + (void)(::testing::GTEST_FLAG(death_test_style) = "threadsafe"); + return RUN_ALL_TESTS(); +} diff --git a/releasenotes/notes/profiling-fix-data-race-segfault-b14f59c06c6bf9ed.yaml b/releasenotes/notes/profiling-fix-data-race-segfault-b14f59c06c6bf9ed.yaml new file mode 100644 index 00000000000..0dcae82aea8 --- /dev/null +++ b/releasenotes/notes/profiling-fix-data-race-segfault-b14f59c06c6bf9ed.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: fix a data race where span information associated with a thread + was read and updated concurrently, leading to segfaults From c5134f9337fdf3fb79431d67dc00826aa6f400cb Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 30 Oct 2024 05:27:45 -0400 Subject: [PATCH 080/372] chore(ci): remove django circleci test suite (#11220) ## Context Recently, the django test suite on CircleCI has been consistently failing, which has been blocking many PRs. While investigating, we observed: - that when we migrated the django test suite to GitLab, we didn't remove it from CircleCI (https://github.com/DataDog/dd-trace-py/pull/10611). - we can reproduce the errors we saw on CI when the testagent container isn't spun up locally - the django tests fail consistently with python 3.8 for the snapshot tests in CI, but consistently pass when run locally with a testagent (we tried with `64b33747062c585df9f0eb810c28d367379a9998`: the head commit on `main`, `0c17d211b9fac9d7855966f70530f19ed17e27a3` : @avara1986 's original django fix PR, and `127e5b72ee45e55e4c6d9b7d07922d47099483fb`: the PR that merged right before @avara1986 's fix PR) The root cause of the CirlceCI issue is still under investigation, but given that this is currently blocking PRs from merging but the same tests are already running AND passing in GitLab, this PR aims to remove the django test suite from CircleCI to unblock contributors. @erikayasuda will continue investigating the root cause of the CircleCI issues, but with this PR merged that investigation will not be a blocker. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index a3a57c1f6ab..ea6d3c9cb39 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -593,15 +593,6 @@ jobs: - run_test: pattern: 'consul' - django: - <<: *machine_executor - parallelism: 4 - steps: - - run_test: - pattern: 'django($|_celery)' - snapshot: true - docker_services: "memcached redis postgres" - mariadb: <<: *machine_executor parallelism: 4 From 1904517ec82f29cce5bf49c13cf1c58a19da7546 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 30 Oct 2024 11:15:46 +0100 Subject: [PATCH 081/372] chore(ci): iast packages tests error (#11199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a comment that says, "Remember to set to False before pushing it!" but it seems I forgot to read it and didn’t do it... :( CI is fixed now: https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/76111/workflows/906992b5-3538-4c86-b996-12d6baaf4249/jobs/4342882 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> --- tests/appsec/iast_packages/test_packages.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 5d6ba9ad54e..66a50b5f7b1 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -34,7 +34,7 @@ # Turn this to True to don't delete the virtualenvs after the tests so debugging can iterate faster. # Remember to set to False before pushing it! -_DEBUG_MODE = True +_DEBUG_MODE = False class PackageForTesting: @@ -127,7 +127,7 @@ def _install(python_cmd, package_name, package_version=""): else: package_fullversion = package_name - cmd = [python_cmd, "-m", "pip", "install", package_fullversion] + cmd = [python_cmd, "-m", "pip", "install", "-U", package_fullversion] env = {} env.update(os.environ) # CAVEAT: we use subprocess instead of `pip.main(["install", package_fullversion])` due to pip package @@ -303,6 +303,7 @@ def uninstall(self, python_cmd): "xn--eckwd4c7c.xn--zckzah", import_module_to_validate="idna.codec", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "importlib-resources", @@ -490,6 +491,9 @@ def uninstall(self, python_cmd): "d8b5635eb590e078a608e083351288a0", "", import_module_to_validate="multipart.multipart", + # This test is failing in CircleCI because, for some reason, instead of installing version + # 0.0.5, it’s installing the latest version + test_import=False, test_propagation=True, ), PackageForTesting( @@ -525,6 +529,7 @@ def uninstall(self, python_cmd): "", import_module_to_validate="rsa.pkcs1", test_propagation=True, + fixme_propagation_fails=True, ), PackageForTesting( "sqlalchemy", From bbffa88bb85239da6c5dd455c633d87e246095c6 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:18:24 +0100 Subject: [PATCH 082/372] feat(asm): add session fingerprinting support (#11203) Improve support for ATO and fingerprints: - session fingerprint is now supported. RC capabilities are updated - add support for `server.business_logic.users.login.failure` and success ephemeral waf addresses - unit tests are updated accordingly to cover for those changes This will also be tested on system tests. https://github.com/DataDog/system-tests/pull/3336 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_capabilities.py | 1 + ddtrace/appsec/_constants.py | 2 ++ ddtrace/appsec/_processor.py | 12 ++++++--- ddtrace/appsec/_trace_utils.py | 8 +++++- .../session_fingerprint-b20cefb1ae3e24dc.yaml | 4 +++ .../appsec/appsec/test_remoteconfiguration.py | 4 +-- tests/appsec/contrib_appsec/utils.py | 26 +++++++++++++++---- 7 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/session_fingerprint-b20cefb1ae3e24dc.yaml diff --git a/ddtrace/appsec/_capabilities.py b/ddtrace/appsec/_capabilities.py index 1b5e46f3f3d..c173f2d6471 100644 --- a/ddtrace/appsec/_capabilities.py +++ b/ddtrace/appsec/_capabilities.py @@ -44,6 +44,7 @@ class Flags(enum.IntFlag): | Flags.ASM_CUSTOM_BLOCKING_RESPONSE | Flags.ASM_EXCLUSION_DATA | Flags.ASM_ENDPOINT_FINGERPRINT + | Flags.ASM_SESSION_FINGERPRINT | Flags.ASM_NETWORK_FINGERPRINT | Flags.ASM_HEADER_FINGERPRINT ) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 513715b2c2a..4b7aafa3e10 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -195,6 +195,8 @@ class WAF_DATA_NAMES(metaclass=Constant_Class): SSRF_ADDRESS: Literal["server.io.net.url"] = "server.io.net.url" SQLI_ADDRESS: Literal["server.db.statement"] = "server.db.statement" SQLI_SYSTEM_ADDRESS: Literal["server.db.system"] = "server.db.system" + LOGIN_FAILURE: Literal["server.business_logic.users.login.failure"] = "server.business_logic.users.login.failure" + LOGIN_SUCCESS: Literal["server.business_logic.users.login.success"] = "server.business_logic.users.login.success" class SPAN_DATA_NAMES(metaclass=Constant_Class): diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 0c242a7cbb2..4ba8222c89a 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -289,10 +289,10 @@ def _waf_action( for key, waf_name in iter_data: # type: ignore[attr-defined] if key in data_already_sent: continue - if waf_name not in WAF_DATA_NAMES.PERSISTENT_ADDRESSES: - value = custom_data.get(key) if custom_data else None - if value: - ephemeral_data[waf_name] = value + # ensure ephemeral addresses are sent, event when value is None + if waf_name not in WAF_DATA_NAMES.PERSISTENT_ADDRESSES and custom_data: + if key in custom_data: + ephemeral_data[waf_name] = custom_data[key] elif self._is_needed(waf_name) or force_keys: value = None @@ -307,6 +307,10 @@ def _waf_action( data_already_sent.add(key) log.debug("[action] WAF got value %s", SPAN_DATA_NAMES.get(key, key)) + # small optimization to avoid running the waf if there is no data to check + if not data and not ephemeral_data: + return None + waf_results = self._ddwaf.run( ctx, data, ephemeral_data=ephemeral_data or None, timeout_ms=asm_config._waf_timeout ) diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py index 0c84d45bca1..a6839f62ce6 100644 --- a/ddtrace/appsec/_trace_utils.py +++ b/ddtrace/appsec/_trace_utils.py @@ -4,7 +4,9 @@ from ddtrace import constants from ddtrace._trace.span import Span from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._asm_request_context import call_waf_callback from ddtrace.appsec._asm_request_context import get_blocked +from ddtrace.appsec._asm_request_context import in_asm_context from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import LOGIN_EVENTS_MODE from ddtrace.appsec._utils import _hash_user_id @@ -113,7 +115,6 @@ def track_user_login_success_event( :param user_id: a string with the UserId :param metadata: a dictionary with additional metadata information to be stored with the event """ - real_mode = login_events_mode if login_events_mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode if real_mode == LOGIN_EVENTS_MODE.DISABLED: return @@ -124,6 +125,9 @@ def track_user_login_success_event( if real_mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str): user_id = _hash_user_id(user_id) + if in_asm_context(): + call_waf_callback(custom_data={"REQUEST_USER_ID": str(user_id), "LOGIN_SUCCESS": real_mode}) + set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span) @@ -166,6 +170,8 @@ def track_user_login_failure_event( span.set_tag_str("%s.failure.email" % APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, email) if name: span.set_tag_str("%s.failure.username" % APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, name) + if in_asm_context(): + call_waf_callback(custom_data={"LOGIN_FAILURE": None}) def track_user_signup_event( diff --git a/releasenotes/notes/session_fingerprint-b20cefb1ae3e24dc.yaml b/releasenotes/notes/session_fingerprint-b20cefb1ae3e24dc.yaml new file mode 100644 index 00000000000..2ea1cc2c820 --- /dev/null +++ b/releasenotes/notes/session_fingerprint-b20cefb1ae3e24dc.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + ASM: Support added for session fingerprints. diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index c2aff2099e2..f00167706dc 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -117,7 +117,7 @@ def test_rc_activation_states_off(tracer, appsec_enabled, rc_value, remote_confi @pytest.mark.parametrize( "rc_enabled, appsec_enabled, capability", [ - (True, "true", "DYHkA/w="), # All capabilities except ASM_ACTIVATION + (True, "true", "D4HkA/w="), # All capabilities except ASM_ACTIVATION (False, "true", ""), (True, "false", "gAAAAA=="), (False, "false", ""), @@ -142,7 +142,7 @@ def test_rc_capabilities(rc_enabled, appsec_enabled, capability, tracer): @pytest.mark.parametrize( "env_rules, expected", [ - ({}, "DYHkA/4="), # All capabilities + ({}, "D4HkA/4="), # All capabilities ({"_asm_static_rule_file": DEFAULT.RULES}, "gAAAAg=="), # Only ASM_FEATURES ], ) diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 566b2a59f61..0712e6d6fd8 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -1469,24 +1469,40 @@ def test_auto_user_events( assert get_tag("usr.id") is None assert not any(tag.startswith("appsec.events.users.login") for tag in root_span()._meta) assert not any(tag.startswith("_dd_appsec.events.users.login") for tag in root_span()._meta) + # check for fingerprints when user events + if asm_enabled and auto_events_enabled and mode != "disabled": + assert get_tag(asm_constants.FINGERPRINTING.HEADER) + assert get_tag(asm_constants.FINGERPRINTING.NETWORK) + assert get_tag(asm_constants.FINGERPRINTING.ENDPOINT) + assert get_tag(asm_constants.FINGERPRINTING.SESSION) + else: + assert get_tag(asm_constants.FINGERPRINTING.HEADER) is None + assert get_tag(asm_constants.FINGERPRINTING.NETWORK) is None + assert get_tag(asm_constants.FINGERPRINTING.ENDPOINT) is None + assert get_tag(asm_constants.FINGERPRINTING.SESSION) is None @pytest.mark.parametrize("asm_enabled", [True, False]) - def test_fingerprinting(self, interface, root_span, get_tag, asm_enabled): + @pytest.mark.parametrize("user_agent", ["dd-test-scanner-log-block", "UnitTestAgent"]) + def test_fingerprinting(self, interface, root_span, get_tag, asm_enabled, user_agent): with override_global_config(dict(_asm_enabled=asm_enabled, _asm_static_rule_file=None)): self.update_tracer(interface) response = interface.client.post( - "/asm/324/huj/?x=1&y=2", headers={"User-Agent": "dd-test-scanner-log-block"}, data={"test": "attack"} + "/asm/324/huj/?x=1&y=2", headers={"User-Agent": user_agent}, data={"test": "attack"} ) - assert self.status(response) == (403 if asm_enabled else 200) - assert get_tag("http.status_code") == ("403" if asm_enabled else "200") - if asm_enabled: + code = 403 if asm_enabled and user_agent == "dd-test-scanner-log-block" else 200 + assert self.status(response) == code + assert get_tag("http.status_code") == str(code) + # check for fingerprints when security events + if asm_enabled and user_agent == "dd-test-scanner-log-block": assert get_tag(asm_constants.FINGERPRINTING.HEADER) assert get_tag(asm_constants.FINGERPRINTING.NETWORK) assert get_tag(asm_constants.FINGERPRINTING.ENDPOINT) + assert get_tag(asm_constants.FINGERPRINTING.SESSION) else: assert get_tag(asm_constants.FINGERPRINTING.HEADER) is None assert get_tag(asm_constants.FINGERPRINTING.NETWORK) is None assert get_tag(asm_constants.FINGERPRINTING.ENDPOINT) is None + assert get_tag(asm_constants.FINGERPRINTING.SESSION) is None def test_iast(self, interface, root_span, get_tag): if interface.name == "fastapi" and asm_config._iast_enabled: From 019ce15257885ebc1d520f5814e80f2ae6a67cf5 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:21:00 +0000 Subject: [PATCH 083/372] chore(ci_visibility): add ATR functionality to CIVisibility and API (#11212) Introduces functionality for Auto Test Retries. On the topic of naming: there are inconsistencies in the public naming of the feature (Auto Test Retries) and the original name ("Flaky Test Retries") which was already established into API responses and published environment variables. This leads to some places where the internal variable/function names match one or the other, mostly based on "proximity" to where the original name is used externally. One note worth making: three environment variables (`DD_CIVISIBILITY_FLAKY_RETRY_ENABLED`, `DD_CIVISIBILITY_FLAKY_RETRY_COUNT` and `DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT`) are being added to control Auto Test Retries, but they are not added to the `ddtrace` `Config` object as usual. This is due to ongoing conversations about changing the way integrations are configured, and to avoid adding items to the config that will need to be deprecated soon. Some minor unrelated changes: - Tweaks to `pytest` imports to fix an issue where some classes were introduced in `7.4.x` instead of `7.0.x` - Early Flake Detection tweaks to tests - Small hatch change to snapshot viewer script Finally, there is no release note since this feature is unreleased and the functionality being added here is not directly user-facing. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_efd_utils.py | 10 +- ddtrace/contrib/pytest/_plugin_v2.py | 15 +- ddtrace/contrib/pytest/_types.py | 16 + ddtrace/internal/ci_visibility/api/_base.py | 2 + .../internal/ci_visibility/api/_session.py | 14 + ddtrace/internal/ci_visibility/api/_test.py | 72 +- ddtrace/internal/ci_visibility/recorder.py | 127 +- .../internal/test_visibility/_atr_mixins.py | 99 + ddtrace/internal/test_visibility/api.py | 6 +- hatch.toml | 1 + .../api/fake_runner_atr_mix_fail.py | 249 ++ .../api/fake_runner_atr_mix_pass.py | 196 ++ .../api/fake_runner_efd_faulty_session.py | 19 +- .../api/test_api_fake_runners.py | 44 + tests/ci_visibility/test_atr.py | 197 ++ tests/ci_visibility/test_efd.py | 4 +- tests/ci_visibility/util.py | 5 +- ...ers.test_manual_api_fake_atr_mix_fail.json | 3006 +++++++++++++++++ ...ers.test_manual_api_fake_atr_mix_pass.json | 1636 +++++++++ tests/utils.py | 1 - 20 files changed, 5684 insertions(+), 35 deletions(-) create mode 100644 ddtrace/contrib/pytest/_types.py create mode 100644 ddtrace/internal/test_visibility/_atr_mixins.py create mode 100644 tests/ci_visibility/api/fake_runner_atr_mix_fail.py create mode 100644 tests/ci_visibility/api/fake_runner_atr_mix_pass.py create mode 100644 tests/ci_visibility/test_atr.py create mode 100644 tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json create mode 100644 tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json diff --git a/ddtrace/contrib/pytest/_efd_utils.py b/ddtrace/contrib/pytest/_efd_utils.py index ca600854417..3a981ab48cd 100644 --- a/ddtrace/contrib/pytest/_efd_utils.py +++ b/ddtrace/contrib/pytest/_efd_utils.py @@ -9,6 +9,8 @@ from ddtrace.contrib.pytest._retry_utils import _efd_get_attempt_string from ddtrace.contrib.pytest._retry_utils import _retry_run_when from ddtrace.contrib.pytest._retry_utils import set_retry_num +from ddtrace.contrib.pytest._types import pytest_TestReport +from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_test_id_from_item from ddtrace.contrib.pytest._utils import _TestOutcome @@ -48,7 +50,7 @@ def efd_handle_retries( test_id: InternalTestId, item: pytest.Item, when: str, - original_result: _pytest.reports.TestReport, + original_result: pytest_TestReport, test_outcome: _TestOutcome, ): # Overwrite the original result to avoid double-counting when displaying totals in final summary @@ -87,7 +89,7 @@ def efd_handle_retries( efd_outcome = _efd_handle_retries(item) - final_report = pytest.TestReport( + final_report = pytest_TestReport( nodeid=item.nodeid, location=item.location, keywords=item.keywords, @@ -98,7 +100,7 @@ def efd_handle_retries( item.ihook.pytest_runtest_logreport(report=final_report) -def efd_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) -> t.List[_pytest.reports.TestReport]: +def efd_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) -> t.List[pytest_TestReport]: return terminalreporter.getreports(_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED) @@ -358,7 +360,7 @@ def efd_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.Te terminalreporter.write_sep("=", purple=True, bold=True) -def efd_get_teststatus(report: _pytest.reports.TestReport) -> t.Optional[pytest.TestShortLogReport]: +def efd_get_teststatus(report: pytest_TestReport) -> t.Optional[pytest_TestShortLogReport]: if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: return pytest.TestShortLogReport( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 73c2d0468ce..922f987d3e0 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -14,10 +14,13 @@ from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled from ddtrace.contrib.pytest._retry_utils import get_retry_num +from ddtrace.contrib.pytest._types import pytest_CallInfo +from ddtrace.contrib.pytest._types import pytest_Config +from ddtrace.contrib.pytest._types import pytest_TestReport +from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_module_path_from_item from ddtrace.contrib.pytest._utils import _get_names_from_item -from ddtrace.contrib.pytest._utils import _get_pytest_version_tuple from ddtrace.contrib.pytest._utils import _get_session_command from ddtrace.contrib.pytest._utils import _get_source_file_info from ddtrace.contrib.pytest._utils import _get_test_id_from_item @@ -60,16 +63,6 @@ from ddtrace.contrib.pytest._efd_utils import efd_handle_retries from ddtrace.contrib.pytest._efd_utils import efd_pytest_terminal_summary_post_yield -if _get_pytest_version_tuple() >= (7, 0, 0): - from pytest import CallInfo as pytest_CallInfo - from pytest import Config as pytest_Config # noqa: F401 - from pytest import TestReport as pytest_TestReport - from pytest import TestShortLogReport as pytest_TestShortLogReport -else: - from _pytest.config import Config as pytest_Config - from _pytest.reports import TestReport as pytest_TestReport - from _pytest.reports import TestReport as pytest_TestShortLogReport - from _pytest.runner import CallInfo as pytest_CallInfo log = get_logger(__name__) diff --git a/ddtrace/contrib/pytest/_types.py b/ddtrace/contrib/pytest/_types.py new file mode 100644 index 00000000000..b9f9b429729 --- /dev/null +++ b/ddtrace/contrib/pytest/_types.py @@ -0,0 +1,16 @@ +from ddtrace.contrib.pytest._utils import _get_pytest_version_tuple + + +if _get_pytest_version_tuple() >= (7, 0, 0): + from pytest import CallInfo as pytest_CallInfo # noqa: F401 + from pytest import Config as pytest_Config # noqa: F401 + from pytest import TestReport as pytest_TestReport # noqa: F401 +else: + from _pytest.config import Config as pytest_Config # noqa: F401 + from _pytest.reports import TestReport as pytest_TestReport # noqa: F401 + from _pytest.runner import CallInfo as pytest_CallInfo # noqa: F401 + +if _get_pytest_version_tuple() >= (7, 4, 0): + from pytest import TestShortLogReport as pytest_TestShortLogReport # noqa: F401 +else: + from _pytest.reports import TestReport as pytest_TestShortLogReport # noqa: F401 diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index 4593db9f110..a9bb68e70be 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -37,6 +37,7 @@ from ddtrace.internal.ci_visibility.telemetry.itr import record_itr_unskippable from ddtrace.internal.constants import COMPONENT from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings from ddtrace.internal.test_visibility.coverage_lines import CoverageLines @@ -69,6 +70,7 @@ class TestVisibilitySessionSettings: itr_correlation_id: str = "" coverage_enabled: bool = False efd_settings: EarlyFlakeDetectionSettings = dataclasses.field(default_factory=EarlyFlakeDetectionSettings) + atr_settings: AutoTestRetriesSettings = dataclasses.field(default_factory=AutoTestRetriesSettings) def __post_init__(self): if not isinstance(self.tracer, Tracer): diff --git a/ddtrace/internal/ci_visibility/api/_session.py b/ddtrace/internal/ci_visibility/api/_session.py index dfaa160f006..61e14c5a552 100644 --- a/ddtrace/internal/ci_visibility/api/_session.py +++ b/ddtrace/internal/ci_visibility/api/_session.py @@ -49,6 +49,8 @@ def __init__( self._efd_is_faulty_session: Optional[bool] = None self._efd_has_efd_failed_tests: bool = False + self._atr_total_retries: int = 0 + self.set_tag(test.ITR_TEST_CODE_COVERAGE_ENABLED, session_settings.coverage_enabled) def _get_hierarchy_tags(self) -> Dict[str, Any]: @@ -152,3 +154,15 @@ def efd_has_failed_tests(self): if _test.efd_has_retries() and _test.efd_get_final_status() == EFDTestStatus.ALL_FAIL: return True return False + + # + # ATR (Auto Test Retries , AKA Flaky Test Retries) functionality + # + def atr_is_enabled(self) -> bool: + return self._session_settings.atr_settings.enabled + + def atr_max_retries_reached(self) -> bool: + return self._atr_total_retries >= self._session_settings.atr_settings.max_session_total_retries + + def _atr_count_retry(self): + self._atr_total_retries += 1 diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index 8a777f43058..83828d4b764 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -47,6 +47,7 @@ def __init__( source_file_info: Optional[TestSourceFileInfo] = None, initial_tags: Optional[Dict[str, str]] = None, is_efd_retry: bool = False, + is_atr_retry: bool = False, resource: Optional[str] = None, is_new: bool = False, ): @@ -72,7 +73,9 @@ def __init__( self._efd_is_retry = is_efd_retry self._efd_retries: List[TestVisibilityTest] = [] self._efd_abort_reason: Optional[str] = None - self._efd_initial_finish_time_ns: Optional[int] = None + + self._atr_is_retry = is_atr_retry + self._atr_retries: List[TestVisibilityTest] = [] # Currently unsupported self._is_benchmark = None @@ -299,3 +302,70 @@ def efd_get_final_status(self) -> EFDTestStatus: def set_efd_abort_reason(self, reason: str) -> None: self._efd_abort_reason = reason + + # + # ATR (Auto Test Retries) functionality + # + def _atr_get_retry_test(self, retry_number: int) -> "TestVisibilityTest": + return self._atr_retries[retry_number - 1] + + def _atr_make_retry_test(self): + retry_test = self.__class__( + self.name, + self._session_settings, + codeowners=self._codeowners, + source_file_info=self._source_file_info, + initial_tags=self._tags, + is_atr_retry=True, + ) + retry_test.parent = self.parent + + return retry_test + + def atr_should_retry(self): + if not self._session_settings.atr_settings.enabled: + return False + + if self.get_session().atr_max_retries_reached(): + return False + + if not self.is_finished(): + log.debug("Auto Test Retries: atr_should_retry called but test is not finished") + return False + + # Only tests that are failing should be retried + if self.atr_get_final_status() != TestStatus.FAIL: + return False + + return len(self._atr_retries) < self._session_settings.atr_settings.max_retries + + def atr_add_retry(self, start_immediately=False) -> Optional[int]: + if not self.atr_should_retry(): + log.debug("Auto Test Retries: atr_add_retry called but test should not retry") + return None + + retry_test = self._atr_make_retry_test() + self._atr_retries.append(retry_test) + session = self.get_session() + if session is not None: + session._atr_count_retry() + + if start_immediately: + retry_test.start() + + return len(self._atr_retries) + + def atr_start_retry(self, retry_number: int): + self._atr_get_retry_test(retry_number).start() + + def atr_finish_retry(self, retry_number: int, status: TestStatus, exc_info: Optional[TestExcInfo] = None): + self._atr_get_retry_test(retry_number).finish_test(status, exc_info=exc_info) + + def atr_get_final_status(self) -> TestStatus: + if self._status in [TestStatus.PASS, TestStatus.SKIP]: + return self._status + + if any(retry._status == TestStatus.PASS for retry in self._atr_retries): + return TestStatus.PASS + + return TestStatus.FAIL diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 2632f70a119..4f4d5e57804 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -26,6 +26,7 @@ from ddtrace.ext.test_visibility.api import TestModule from ddtrace.ext.test_visibility.api import TestModuleId from ddtrace.ext.test_visibility.api import TestSession +from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.ext.test_visibility.api import TestSuite from ddtrace.ext.test_visibility.api import TestSuiteId from ddtrace.internal import agent @@ -71,6 +72,8 @@ from ddtrace.internal.compat import parse from ddtrace.internal.logger import get_logger from ddtrace.internal.service import Service +from ddtrace.internal.test_visibility._atr_mixins import ATRTestMixin +from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings from ddtrace.internal.test_visibility._efd_mixins import EFDTestMixin from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -277,6 +280,7 @@ def __init__(self, tracer=None, config=None, service=None): "API-provided settings: Early Flake Detection enabled: %s", self._api_settings.early_flake_detection.enabled, ) + log.info("API-provided settings: Auto Test Retries enabled: %s", self._api_settings.flaky_test_retries_enabled) log.info("Detected configurations: %s", str(self._configurations)) try: @@ -408,6 +412,14 @@ def is_efd_enabled(cls): and ddconfig._test_visibility_early_flake_detection_enabled ) + @classmethod + def is_atr_enabled(cls): + if cls._instance is None: + return False + return cls._instance._api_settings.flaky_test_retries_enabled and asbool( + os.getenv("DD_CIVISIBILITY_FLAKY_RETRY_ENABLED", default=True) + ) + @classmethod def should_collect_coverage(cls): return cls._instance._api_settings.coverage_enabled or asbool( @@ -505,10 +517,14 @@ def enable(cls, tracer=None, config=None, service=None): log.debug("%s enabled", cls.__name__) log.info( - "Final settings: coverage collection: %s, test skipping: %s, Early Flake Detection: %s", + "Final settings: coverage collection: %s, " + "test skipping: %s, " + "Early Flake Detection: %s, " + "Auto Test Retries: %s", cls._instance._collect_coverage_enabled, CIVisibility.test_skipping_enabled(), CIVisibility.is_efd_enabled(), + CIVisibility.is_atr_enabled(), ) @classmethod @@ -561,6 +577,14 @@ def _start_service(self): "DD_TEST_VISIBILITY_EARLY_FLAKE_DETECTION_ENABLED environment variable" ) + if self._api_settings.flaky_test_retries_enabled and not asbool( + os.environ.get("DD_CIVISIBILITY_FLAKY_RETRY_ENABLED", True) + ): + log.warning( + "Auto Test Retries is enabled by API but disabled by " + "DD_CIVISIBILITY_FLAKY_RETRY_ENABLED environment variable" + ) + def _stop_service(self): # type: () -> None if self._should_upload_git_metadata and not self._git_client.metadata_upload_finished(): @@ -728,6 +752,49 @@ def get_efd_api_settings(cls) -> Optional[EarlyFlakeDetectionSettings]: return None return instance._api_settings.early_flake_detection + @classmethod + def get_atr_api_settings(cls) -> Optional[AutoTestRetriesSettings]: + if not cls.enabled: + error_msg = "CI Visibility is not enabled" + log.warning(error_msg) + raise CIVisibilityError(error_msg) + instance = cls.get_instance() + if instance is None or instance._api_settings is None: + return None + + if instance._api_settings.flaky_test_retries_enabled: + # NOTE: this is meant to come from integration settings but current plans to rewrite how integration + # settings are defined make it better for this logic to be temporarily defined here. + + # defaults + max_retries = 5 + max_session_total_retries = 1000 + + env_max_retries = os.environ.get("DD_CIVISIBILITY_FLAKY_RETRY_COUNT") + if env_max_retries is not None: + try: + max_retries = int(env_max_retries) + except ValueError: + log.warning( + "Failed to parse DD_CIVISIBILITY_FLAKY_RETRY_COUNT, using default value: %s", max_retries + ) + + env_max_session_total_retries = os.environ.get("DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT") + if env_max_session_total_retries is not None: + try: + max_session_total_retries = int(env_max_session_total_retries) + except ValueError: + log.warning( + "Failed to parse DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT, using default value: %s", + max_session_total_retries, + ) + + return AutoTestRetriesSettings( + enabled=True, max_retries=max_retries, max_session_total_retries=max_session_total_retries + ) + + return None + @classmethod def get_workspace_path(cls) -> Optional[str]: if not cls.enabled: @@ -834,10 +901,13 @@ def _on_discover_session( test_framework_telemetry_name = test_framework_telemetry_name or TEST_FRAMEWORKS.MANUAL efd_api_settings = CIVisibility.get_efd_api_settings() - if efd_api_settings is None: - log.debug("Could not get Early Flake Detection settings, using defaults") + if efd_api_settings is None or not CIVisibility.is_efd_enabled(): efd_api_settings = EarlyFlakeDetectionSettings() + atr_api_settings = CIVisibility.get_atr_api_settings() + if atr_api_settings is None or not CIVisibility.is_atr_enabled(): + atr_api_settings = AutoTestRetriesSettings() + session_settings = TestVisibilitySessionSettings( tracer=tracer, test_service=test_service, @@ -858,6 +928,7 @@ def _on_discover_session( itr_correlation_id=instance._itr_meta.get(ITR_CORRELATION_ID_TAG_NAME, ""), coverage_enabled=CIVisibility.should_collect_coverage(), efd_settings=efd_api_settings, + atr_settings=atr_api_settings, ) session = TestVisibilitySession( @@ -907,6 +978,12 @@ def _on_session_get_codeowners() -> Optional[Codeowners]: return CIVisibility.get_codeowners() +@_requires_civisibility_enabled +def _on_session_is_atr_enabled() -> bool: + log.debug("Getting Auto Test Retries enabled") + return CIVisibility.is_atr_enabled() + + @_requires_civisibility_enabled def _on_session_is_efd_enabled() -> bool: log.debug("Getting Early Flake Detection enabled") @@ -936,6 +1013,7 @@ def _register_session_handlers(): core.on("test_visibility.session.get_codeowners", _on_session_get_codeowners, "codeowners") core.on("test_visibility.session.get_path_codeowners", _on_session_get_path_codeowners, "path_codeowners") core.on("test_visibility.session.get_workspace_path", _on_session_get_workspace_path, "workspace_path") + core.on("test_visibility.session.is_atr_enabled", _on_session_is_atr_enabled, "is_atr_enabled") core.on("test_visibility.session.is_efd_enabled", _on_session_is_efd_enabled, "is_efd_enabled") core.on( "test_visibility.session.should_collect_coverage", @@ -1305,6 +1383,48 @@ def _register_efd_handlers(): core.on("test_visibility.efd.get_final_status", _on_efd_get_final_status, "efd_final_status") +@_requires_civisibility_enabled +def _on_atr_is_enabled() -> bool: + return CIVisibility.is_atr_enabled() + + +@_requires_civisibility_enabled +def _on_atr_should_retry_test(item_id: InternalTestId) -> bool: + return CIVisibility.get_test_by_id(item_id).atr_should_retry() + + +@_requires_civisibility_enabled +def _on_atr_add_retry(item_id: InternalTestId, retry_number: int) -> Optional[int]: + return CIVisibility.get_test_by_id(item_id).atr_add_retry(retry_number) + + +@_requires_civisibility_enabled +def _on_atr_start_retry(test_id: InternalTestId, retry_number: int): + CIVisibility.get_test_by_id(test_id).atr_start_retry(retry_number) + + +@_requires_civisibility_enabled +def _on_atr_finish_retry(atr_finish_args: ATRTestMixin.ATRRetryFinishArgs): + CIVisibility.get_test_by_id(atr_finish_args.test_id).atr_finish_retry( + atr_finish_args.retry_number, atr_finish_args.status, atr_finish_args.exc_info + ) + + +@_requires_civisibility_enabled +def _on_atr_get_final_status(test_id: InternalTestId) -> TestStatus: + return CIVisibility.get_test_by_id(test_id).atr_get_final_status() + + +def _register_atr_handlers(): + log.debug("Registering ATR handlers") + core.on("test_visibility.atr.is_enabled", _on_atr_is_enabled, "is_enabled") + core.on("test_visibility.atr.should_retry_test", _on_atr_should_retry_test, "should_retry_test") + core.on("test_visibility.atr.add_retry", _on_atr_add_retry, "retry_number") + core.on("test_visibility.atr.start_retry", _on_atr_start_retry) + core.on("test_visibility.atr.finish_retry", _on_atr_finish_retry) + core.on("test_visibility.atr.get_final_status", _on_atr_get_final_status, "atr_final_status") + + _register_session_handlers() _register_module_handlers() _register_suite_handlers() @@ -1314,3 +1434,4 @@ def _register_efd_handlers(): _register_coverage_handlers() _register_itr_handlers() _register_efd_handlers() +_register_atr_handlers() diff --git a/ddtrace/internal/test_visibility/_atr_mixins.py b/ddtrace/internal/test_visibility/_atr_mixins.py new file mode 100644 index 00000000000..c4f86131eb9 --- /dev/null +++ b/ddtrace/internal/test_visibility/_atr_mixins.py @@ -0,0 +1,99 @@ +import dataclasses +import typing as t + +from ddtrace.ext.test_visibility._utils import _catch_and_log_exceptions +import ddtrace.ext.test_visibility.api as ext_api +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId + + +log = get_logger(__name__) + + +@dataclasses.dataclass +class AutoTestRetriesSettings: + enabled: bool = False + max_retries: int = 5 + max_session_total_retries: int = 1000 + + +class ATRSessionMixin: + @staticmethod + @_catch_and_log_exceptions + def atr_is_enabled() -> bool: + log.debug("Checking if Auto Test Retries is enabled for session") + is_enabled = core.dispatch_with_results("test_visibility.atr.is_enabled").is_enabled.value + log.debug("Auto Test Retries enabled: %s", is_enabled) + return is_enabled + + +class ATRTestMixin: + @staticmethod + @_catch_and_log_exceptions + def atr_should_retry(item_id: InternalTestId) -> bool: + log.debug("Checking if item %s should be retried for Auto Test Retries", item_id) + should_retry_test = core.dispatch_with_results( + "test_visibility.atr.should_retry_test", (item_id,) + ).should_retry_test.value + log.debug("Item %s should be retried: %s", item_id, should_retry_test) + return should_retry_test + + @staticmethod + @_catch_and_log_exceptions + def atr_add_retry(item_id: InternalTestId, start_immediately: bool = False) -> int: + log.debug("Adding Auto Test Retries retry for item %s", item_id) + retry_number = core.dispatch_with_results( + "test_visibility.atr.add_retry", (item_id, start_immediately) + ).retry_number.value + log.debug("Added Auto Test Retries retry %s for item %s", retry_number, item_id) + return retry_number + + @staticmethod + @_catch_and_log_exceptions + def atr_start_retry(item_id: InternalTestId) -> None: + log.debug("Starting retry for item %s", item_id) + core.dispatch("test_visibility.atr.start_retry", (item_id,)) + + class ATRRetryFinishArgs(t.NamedTuple): + test_id: InternalTestId + retry_number: int + status: ext_api.TestStatus + skip_reason: t.Optional[str] = None + exc_info: t.Optional[ext_api.TestExcInfo] = None + + @staticmethod + @_catch_and_log_exceptions + def atr_finish_retry( + item_id: InternalTestId, + retry_number: int, + status: ext_api.TestStatus, + skip_reason: t.Optional[str] = None, + exc_info: t.Optional[ext_api.TestExcInfo] = None, + ): + log.debug( + "Finishing ATR test retry %s for item %s, status: %s, skip_reason: %s, exc_info: %s", + retry_number, + item_id, + status, + skip_reason, + exc_info, + ) + core.dispatch( + "test_visibility.atr.finish_retry", + ( + ATRTestMixin.ATRRetryFinishArgs( + item_id, retry_number, status, skip_reason=skip_reason, exc_info=exc_info + ), + ), + ) + + @staticmethod + @_catch_and_log_exceptions + def atr_get_final_status(item_id: InternalTestId) -> ext_api.TestStatus: + log.debug("Getting final ATR status for item %s", item_id) + atr_final_status = core.dispatch_with_results( + "test_visibility.atr.get_final_status", (item_id,) + ).atr_final_status.value + log.debug("Final ATR status for item %s: %s", item_id, atr_final_status) + return atr_final_status diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index 618dcf2499b..6f56f290321 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -12,6 +12,8 @@ from ddtrace.internal import core from ddtrace.internal.codeowners import Codeowners as _Codeowners from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._atr_mixins import ATRSessionMixin +from ddtrace.internal.test_visibility._atr_mixins import ATRTestMixin from ddtrace.internal.test_visibility._efd_mixins import EFDSessionMixin from ddtrace.internal.test_visibility._efd_mixins import EFDTestMixin from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -29,7 +31,7 @@ def get_span(item_id: t.Union[ext_api.TestVisibilityItemId, InternalTestId]) -> return _get_item_span(item_id) -class InternalTestSession(ext_api.TestSession, EFDSessionMixin): +class InternalTestSession(ext_api.TestSession, EFDSessionMixin, ATRSessionMixin): @staticmethod def get_span() -> Span: return _get_item_span(TestSessionId()) @@ -110,7 +112,7 @@ class InternalTestSuite(ext_api.TestSuite, InternalTestBase, ITRMixin): pass -class InternalTest(ext_api.Test, InternalTestBase, ITRMixin, EFDTestMixin): +class InternalTest(ext_api.Test, InternalTestBase, ITRMixin, EFDTestMixin, ATRTestMixin): class FinishArgs(NamedTuple): """InternalTest allows finishing with an overridden finish time (for EFD and other retry purposes)""" diff --git a/hatch.toml b/hatch.toml index ae0f6abafff..b7ed9ce6097 100644 --- a/hatch.toml +++ b/hatch.toml @@ -438,6 +438,7 @@ pytest = ["~=6.0", "~=7.0", "~=8.0"] [envs.snapshot_viewer] dev-mode = false +detached = true template = "snapshot_viewer" dependencies = [ "PrettyPrintTree" diff --git a/tests/ci_visibility/api/fake_runner_atr_mix_fail.py b/tests/ci_visibility/api/fake_runner_atr_mix_fail.py new file mode 100644 index 00000000000..ac47829fe08 --- /dev/null +++ b/tests/ci_visibility/api/fake_runner_atr_mix_fail.py @@ -0,0 +1,249 @@ +"""Fake test runner that uses ATR where retried tests fail + +Also: +- tests that setting a custom retry count is respected, so must be invoked with DD_CIVISIBILITY_FLAKY_RETRY_COUNT=7 +- tests that max session retry count is respected, so must be invoked with DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT=20 + +Comment lines in the test start/finish lines are there for visual distinction. +""" +import json +from multiprocessing import freeze_support +from pathlib import Path +from unittest import mock + +from ddtrace.ext.test_visibility import api as ext_api +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings +from ddtrace.internal.test_visibility import api + + +def run_tests(): + # START DISCOVERY + api.InternalTestSession.discover("manual_atr_mix_fail", "dd_manual_test_fw", "1.0.0") + api.InternalTestSession.start() + + # M1 + + m1_id = ext_api.TestModuleId("m1") + + api.InternalTestModule.discover(m1_id) + + # M1_S1 + + m1_s1_id = ext_api.TestSuiteId(m1_id, "m1_s1") + api.InternalTestSuite.discover(m1_s1_id) + + # M1_S1 tests + + m1_s1_t1_id = api.InternalTestId(m1_s1_id, "m1_s1_t1") + api.InternalTest.discover(m1_s1_t1_id, source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 1, 2)) + + m1_s1_t2_id = api.InternalTestId(m1_s1_id, "m1_s1_t2") + api.InternalTest.discover(m1_s1_t2_id, source_file_info=None) + + m1_s1_t3_id = api.InternalTestId(m1_s1_id, "m1_s1_t3") + api.InternalTest.discover( + m1_s1_t3_id, + codeowners=["@romain", "@romain2"], + source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 4, 12), + ) + + m1_s1_t4_p1_id = api.InternalTestId(m1_s1_id, "m1_s1_t4_p1", parameters=json.dumps({"param1": "value1"})) + api.InternalTest.discover(m1_s1_t4_p1_id) + m1_s1_t4_p2_id = api.InternalTestId(m1_s1_id, "m1_s1_t4_p2_id", parameters=json.dumps({"param1": "value2"})) + api.InternalTest.discover(m1_s1_t4_p2_id) + m1_s1_t4_p3_id = api.InternalTestId(m1_s1_id, "m1_s1_t4_p3_id", parameters=json.dumps({"param1": "value3"})) + api.InternalTest.discover(m1_s1_t4_p3_id) + + # M2 + + m2_id = ext_api.TestModuleId("m2") + api.InternalTestModule.discover(m2_id) + + # M2_S1 + + m2_s1_id = ext_api.TestSuiteId(m2_id, "m2_s1") + api.InternalTestSuite.discover(m2_s1_id) + + # M2_S1 tests all pass + m2_s1_test_ids = [ + api.InternalTestId(m2_s1_id, "m2_s1_t1"), + api.InternalTestId(m2_s1_id, "m2_s1_t2"), + api.InternalTestId(m2_s1_id, "m2_s1_t3"), + api.InternalTestId(m2_s1_id, "m2_s1_t4"), + api.InternalTestId(m2_s1_id, "m2_s1_t5"), + api.InternalTestId(m2_s1_id, "m2_s1_t6"), + api.InternalTestId(m2_s1_id, "m2_s1_t7"), + api.InternalTestId(m2_s1_id, "m2_s1_t8"), + api.InternalTestId(m2_s1_id, "m2_s1_t9"), + ] + for test_id in m2_s1_test_ids: + api.InternalTest.discover(test_id) + + # M2_S2 + + m2_s2_id = ext_api.TestSuiteId(m2_id, "m2_s2") + api.InternalTestSuite.discover(m2_s2_id) + + # M2_S2 tests + + m2_s2_t1_id = api.InternalTestId(m2_s2_id, "m2_s2_t1") + api.InternalTest.discover(m2_s2_t1_id, source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 1, 2)) + + m2_s2_t2_id = api.InternalTestId(m2_s2_id, "m2_s2_t2") + api.InternalTest.discover(m2_s2_t2_id) + + m2_s2_t3_id = api.InternalTestId(m2_s2_id, "m2_s2_t3") + api.InternalTest.discover( + m2_s2_t3_id, + codeowners=["@romain"], + source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 4, 12), + ) + + # END DISCOVERY + + # START TESTS + + # START M1 + + api.InternalTestModule.start(m1_id) + + # START M1_S1 + + api.InternalTestSuite.start(m1_s1_id) + + # m1_s1_t1 test expect 7 ATR retries + api.InternalTest.start(m1_s1_t1_id) + api.InternalTest.finish(m1_s1_t1_id, TestStatus.FAIL) + + m1_s1_t1_retry_count = 0 + while api.InternalTest.atr_should_retry(m1_s1_t1_id): + m1_s1_t1_retry_count += 1 + m1_s1_t1_retry_number = api.InternalTest.atr_add_retry(m1_s1_t1_id, start_immediately=True) + api.InternalTest.atr_finish_retry(m1_s1_t1_id, m1_s1_t1_retry_number, TestStatus.FAIL) + assert m1_s1_t1_retry_count == 7, "Expected 7 ATR retries, got %s" % m1_s1_t1_retry_count + m1_s1_t1_final_status = api.InternalTest.atr_get_final_status(m1_s1_t1_id) + assert m1_s1_t1_final_status == TestStatus.FAIL, "Expected final status to be FAIL, got %s" % m1_s1_t1_final_status + + # m1_s1_t2 test: expect 7 ATR retries, passes on last attempt + api.InternalTest.start(m1_s1_t2_id) + api.InternalTest.finish(m1_s1_t2_id, TestStatus.FAIL) + + m1_s1_t2_retry_count = 0 + while api.InternalTest.atr_should_retry(m1_s1_t2_id): + m1_s1_t2_retry_count += 1 + m1_s1_t2_retry_number = api.InternalTest.atr_add_retry(m1_s1_t2_id, start_immediately=True) + api.InternalTest.atr_finish_retry( + m1_s1_t2_id, m1_s1_t2_retry_number, TestStatus.PASS if m1_s1_t2_retry_count == 7 else TestStatus.FAIL + ) + assert m1_s1_t2_retry_count == 7, "Expected 7 ATR retries, got %s" % m1_s1_t2_retry_count + m1_s1_t2_final_status = api.InternalTest.atr_get_final_status(m1_s1_t2_id) + assert m1_s1_t2_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t2_final_status + + # m1_s1_t3 test: expect no retries (skipped) + api.InternalTest.start(m1_s1_t3_id) + api.InternalTest.mark_skip(m1_s1_t3_id) + assert not api.InternalTest.atr_should_retry(m1_s1_t3_id), "Should not retry: first attempt skipped" + + # Parametrized tests should only be retried if the first status fails + api.InternalTest.start(m1_s1_t4_p1_id) + api.InternalTest.finish(m1_s1_t4_p1_id, TestStatus.PASS) + assert not api.InternalTest.atr_should_retry(m1_s1_t4_p1_id), "Should not retry: first attempt passed" + + api.InternalTest.start(m1_s1_t4_p2_id) + api.InternalTest.mark_fail(m1_s1_t4_p2_id) + m1_s1_t4_p2_retry_count = 0 + while api.InternalTest.atr_should_retry(m1_s1_t4_p2_id): + m1_s1_t4_p2_retry_count += 1 + m1_s1_t4_p2_retry_number = api.InternalTest.atr_add_retry(m1_s1_t4_p2_id, start_immediately=True) + api.InternalTest.atr_finish_retry( + m1_s1_t4_p2_id, + m1_s1_t4_p2_retry_number, + TestStatus.PASS if m1_s1_t4_p2_retry_count > 4 else TestStatus.FAIL, + ) + assert m1_s1_t4_p2_retry_count == 5, "Expected 5 ATR retries, got %s" % m1_s1_t4_p2_retry_count + m1_s1_t4_p2_final_status = api.InternalTest.atr_get_final_status(m1_s1_t4_p2_id) + assert m1_s1_t4_p2_final_status == TestStatus.PASS, ( + "Expected final status to be PASS, got %s" % m1_s1_t4_p2_final_status + ) + + api.InternalTest.start(m1_s1_t4_p3_id) + api.InternalTest.mark_skip(m1_s1_t4_p3_id) + assert not api.InternalTest.atr_should_retry(m1_s1_t4_p3_id), "Should not retry: first attempt skipped" + + api.InternalTestSuite.finish(m1_s1_id) + + # END M1_S1 + + api.InternalTestModule.finish(m1_id) + + # END M1 + + # START M2 + + api.InternalTestModule.start(m2_id) + + # START M2_S1 + + api.InternalTestSuite.start(m2_s1_id) + + for test_id in m2_s1_test_ids: + api.InternalTest.start(test_id) + api.InternalTest.mark_pass(test_id) + assert not api.InternalTest.atr_should_retry(test_id), "Should not retry: passed first attempt" + + api.InternalTestSuite.finish(m2_s1_id) + + # END M2_S1 + + # START M2_S2 + + api.InternalTestSuite.start(m2_s2_id) + + # should not be retried (skipped) + api.InternalTest.start(m2_s2_t1_id) + api.InternalTest.mark_skip(m2_s2_t1_id) + assert not api.InternalTest.atr_should_retry(m2_s2_t1_id), "Should not retry: skipped first attempt" + + # m2_s2_t2 test: expects 1 retries, then fail because total session reries max is reached + api.InternalTest.start(m2_s2_t2_id) + api.InternalTest.finish(m2_s2_t2_id, TestStatus.FAIL) + + m2_s2_t2_retry_count = 0 + while api.InternalTest.atr_should_retry(m2_s2_t2_id): + m2_s2_t2_retry_count += 1 + m2_s2_t2_retry_number = api.InternalTest.atr_add_retry(m2_s2_t2_id, start_immediately=True) + api.InternalTest.atr_finish_retry(m2_s2_t2_id, m2_s2_t2_retry_number, TestStatus.FAIL) + assert m2_s2_t2_retry_count == 1, "Expected 1 ATR retries, got %s" % m2_s2_t2_retry_count + m2_s2_t2_final_status = api.InternalTest.atr_get_final_status(m2_s2_t2_id) + assert m2_s2_t2_final_status == TestStatus.FAIL, "Expected final status to be FAIL, got %s" % m2_s2_t2_final_status + + # should not be retried (passed) + api.InternalTest.start(m2_s2_t3_id) + api.InternalTest.mark_pass(m2_s2_t3_id) + assert not api.InternalTest.atr_should_retry(m2_s2_t3_id), "Should not retry: passed first attempt" + + api.InternalTestSuite.finish(m2_s2_id) + + api.InternalTestModule.finish(m2_id) + + api.InternalTestSession.finish() + + # FINISH TESTS + + +def main(): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings(require_git=False, flaky_test_retries_enabled=True), + ): + ext_api.enable_test_visibility() + + run_tests() + + ext_api.disable_test_visibility() + + +if __name__ == "__main__": + freeze_support() + main() diff --git a/tests/ci_visibility/api/fake_runner_atr_mix_pass.py b/tests/ci_visibility/api/fake_runner_atr_mix_pass.py new file mode 100644 index 00000000000..5acad30e779 --- /dev/null +++ b/tests/ci_visibility/api/fake_runner_atr_mix_pass.py @@ -0,0 +1,196 @@ +"""Fake test runner that uses ATR where some of the test retries fail, but the test passes + +Comment lines in the test start/finish lines are there for visual distinction. +""" +import json +from multiprocessing import freeze_support +from pathlib import Path +from unittest import mock + +from ddtrace.ext.test_visibility import api as ext_api +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings +from ddtrace.internal.test_visibility import api +from ddtrace.internal.test_visibility.api import InternalTest + + +def run_tests(): + # START DISCOVERY + api.InternalTestSession.discover("manual_atr_mix_pass", "dd_manual_test_fw", "1.0.0") + api.InternalTestSession.start() + + # M1 + + m1_id = ext_api.TestModuleId("m1") + + api.InternalTestModule.discover(m1_id) + + # M1_S1 + + m1_s1_id = ext_api.TestSuiteId(m1_id, "m1_s1") + api.InternalTestSuite.discover(m1_s1_id) + + # M1_S1 tests + + m1_s1_t1_id = api.InternalTestId(m1_s1_id, "m1_s1_t1") + api.InternalTest.discover(m1_s1_t1_id, source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 1, 2)) + + m1_s1_t2_id = api.InternalTestId(m1_s1_id, "m1_s1_t2") + api.InternalTest.discover(m1_s1_t2_id, source_file_info=None) + + m1_s1_t3_id = api.InternalTestId(m1_s1_id, "m1_s1_t3") + api.InternalTest.discover( + m1_s1_t3_id, + codeowners=["@romain", "@romain2"], + source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 4, 12), + ) + + # NOTE: these parametrized tests will not be retried + m1_s1_t4_p1_id = api.InternalTestId(m1_s1_id, "m1_s1_t4_p1", parameters=json.dumps({"param1": "value1"})) + api.InternalTest.discover(m1_s1_t4_p1_id) + m1_s1_t4_p2_id = api.InternalTestId(m1_s1_id, "m1_s1_t4_p2_id", parameters=json.dumps({"param1": "value2"})) + api.InternalTest.discover(m1_s1_t4_p2_id) + + # M2 + + m2_id = ext_api.TestModuleId("m2") + api.InternalTestModule.discover(m2_id) + + # M2_S1 + m2_s1_id = ext_api.TestSuiteId(m2_id, "m2_s1") + api.InternalTestSuite.discover(m2_s1_id) + + # M2_S1 tests + + m2_s1_t1_id = api.InternalTestId(m2_s1_id, "m2_s1_t1") + api.InternalTest.discover(m2_s1_t1_id, source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 1, 2)) + + m2_s1_t2_id = api.InternalTestId(m2_s1_id, "m2_s1_t2") + api.InternalTest.discover(m2_s1_t2_id) + + m2_s1_t3_id = api.InternalTestId(m2_s1_id, "m2_s1_t3") + api.InternalTest.discover( + m2_s1_t3_id, + codeowners=["@romain"], + source_file_info=ext_api.TestSourceFileInfo(Path("my_file_1.py"), 4, 12), + ) + + # END DISCOVERY + + # START TESTS + + # START M1 + + api.InternalTestModule.start(m1_id) + + # START M1_S1 + + api.InternalTestSuite.start(m1_s1_id) + + # m1_s1_t1 test expect to pass on the 4th retry + api.InternalTest.start(m1_s1_t1_id) + api.InternalTest.finish(m1_s1_t1_id, TestStatus.FAIL) + + m1_s1_t1_retry_count = 0 + while api.InternalTest.atr_should_retry(m1_s1_t1_id): + m1_s1_t1_retry_count += 1 + m1_s1_t1_retry_number = api.InternalTest.atr_add_retry(m1_s1_t1_id, start_immediately=True) + api.InternalTest.atr_finish_retry( + m1_s1_t1_id, m1_s1_t1_retry_number, TestStatus.PASS if m1_s1_t1_retry_count % 4 == 0 else TestStatus.FAIL + ) + assert m1_s1_t1_retry_count == 4, "Expected 4 ATR retries, got %s" % m1_s1_t1_retry_count + m1_s1_t1_final_status = api.InternalTest.atr_get_final_status(m1_s1_t1_id) + assert m1_s1_t1_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t1_final_status + + # m1_s1_t2 test: expect to pass on the 2nd retry + api.InternalTest.start(m1_s1_t2_id) + api.InternalTest.mark_fail(m1_s1_t2_id) + + m1_s1_t2_retry_count = 0 + while api.InternalTest.atr_should_retry(m1_s1_t2_id): + m1_s1_t2_retry_count += 1 + m1_s1_t2_retry_number = api.InternalTest.atr_add_retry(m1_s1_t2_id, start_immediately=True) + api.InternalTest.atr_finish_retry( + m1_s1_t2_id, m1_s1_t2_retry_number, TestStatus.PASS if m1_s1_t2_retry_count > 1 else TestStatus.FAIL + ) + assert m1_s1_t2_retry_count == 2, "Expected 2 ATR retries, got %s" % m1_s1_t2_retry_count + m1_s1_t2_final_status = api.InternalTest.atr_get_final_status(m1_s1_t2_id) + assert m1_s1_t2_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m1_s1_t2_final_status + + # m1_s1_t3 test: expect no retries (it passed on the first try) + api.InternalTest.start(m1_s1_t3_id) + api.InternalTest.mark_pass(m1_s1_t3_id) + assert not api.InternalTest.atr_should_retry(m1_s1_t3_id), "Should not retry: passed first attempt" + + # m1_s1_t4 parametrized tests and ATR retries (but they pass or skip and should not retry) + api.InternalTest.start(m1_s1_t4_p1_id) + api.InternalTest.mark_skip(m1_s1_t4_p1_id) + assert not api.InternalTest.atr_should_retry(m1_s1_t4_p1_id), "Should not retry: skipped first attempt" + api.InternalTest.start(m1_s1_t4_p2_id) + api.InternalTest.mark_pass(m1_s1_t4_p2_id) + assert not api.InternalTest.atr_should_retry(m1_s1_t4_p2_id), "Should not retry: passed first attempt" + + api.InternalTestSuite.finish(m1_s1_id) + + # END M1_S1 + + api.InternalTestModule.finish(m1_id) + + # END M1 + + # START M2 + + api.InternalTestModule.start(m2_id) + + # START M2_S1 + + api.InternalTestSuite.start(m2_s1_id) + + # should not be retried (skipped) + api.InternalTest.start(m2_s1_t1_id) + api.InternalTest.mark_skip(m2_s1_t1_id) + assert not api.InternalTest.atr_should_retry(m2_s1_t1_id), "Should not retry: skipped" + + # expect to pass on 5th retry, and skips every other + api.InternalTest.start(m2_s1_t2_id) + api.InternalTest.mark_fail(m2_s1_t2_id) + m2_s1_t2_retry_count = 0 + while InternalTest.atr_should_retry(m2_s1_t2_id): + m2_s1_t2_retry_count += 1 + m2_s1_t2_retry_number = InternalTest.atr_add_retry(m2_s1_t2_id, start_immediately=True) + InternalTest.atr_finish_retry( + m2_s1_t2_id, m2_s1_t2_retry_number, TestStatus.PASS if m2_s1_t2_retry_count == 5 else TestStatus.SKIP + ) + assert m2_s1_t2_retry_count == 5, "Expected 5 ATR retries, got %s" % m2_s1_t2_retry_count + m2_s1_t2_final_status = InternalTest.atr_get_final_status(m2_s1_t2_id) + assert m2_s1_t2_final_status == TestStatus.PASS, "Expected final status to be PASS, got %s" % m2_s1_t2_final_status + + # should not be retried (max session retry count exceeded) + api.InternalTest.start(m2_s1_t3_id) + api.InternalTest.mark_pass(m2_s1_t3_id) + assert not api.InternalTest.atr_should_retry(m2_s1_t3_id), "Should not retry: passed first attempt" + + api.InternalTestSuite.finish(m2_s1_id) + + api.InternalTestModule.finish(m2_id) + + api.InternalTestSession.finish() + + # FINISH TESTS + + +def main(): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings(require_git=False, flaky_test_retries_enabled=True), + ): + ext_api.enable_test_visibility() + + run_tests() + + ext_api.disable_test_visibility() + + +if __name__ == "__main__": + freeze_support() + main() diff --git a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py index b6058c607fd..ea841888de6 100644 --- a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py +++ b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py @@ -148,23 +148,21 @@ def run_tests(): api.InternalTest.start(m1_s1_t1_id) api.InternalTest.mark_pass(m1_s1_t1_id) assert not api.InternalTest.efd_should_retry(m1_s1_t1_id), "Should not retry: session is faulty" - api.InternalTest.mark_pass(m1_s1_t1_id) api.InternalTest.start(m1_s1_t2_id) api.InternalTest.mark_pass(m1_s1_t2_id) assert not api.InternalTest.efd_should_retry(m1_s1_t2_id), "Should not retry: session is faulty" - api.InternalTest.mark_pass(m1_s1_t2_id) api.InternalTest.start(m1_s1_t3_id) - assert not api.InternalTest.efd_should_retry(m1_s1_t3_id), "Should not retry: session is faulty" api.InternalTest.mark_skip(m1_s1_t3_id) + assert not api.InternalTest.efd_should_retry(m1_s1_t3_id), "Should not retry: session is faulty" api.InternalTest.start(m1_s1_t4_p1_id) - assert not api.InternalTest.efd_should_retry(m1_s1_t4_p1_id), "Should not retry: session is faulty" api.InternalTest.mark_skip(m1_s1_t4_p1_id) + assert not api.InternalTest.efd_should_retry(m1_s1_t4_p1_id), "Should not retry: session is faulty" api.InternalTest.start(m1_s1_t4_p2_id) - assert not api.InternalTest.efd_should_retry(m1_s1_t4_p2_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m1_s1_t4_p2_id) + assert not api.InternalTest.efd_should_retry(m1_s1_t4_p2_id), "Should not retry: session is faulty" api.InternalTestSuite.finish(m1_s1_id) @@ -184,8 +182,8 @@ def run_tests(): for test_id in m2_s1_test_ids: api.InternalTest.start(test_id) - assert not api.InternalTest.efd_should_retry(test_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(test_id) + assert not api.InternalTest.efd_should_retry(test_id), "Should not retry: session is faulty" api.InternalTestSuite.finish(m2_s1_id) @@ -196,25 +194,24 @@ def run_tests(): api.InternalTestSuite.start(m2_s2_id) api.InternalTest.start(m2_s2_t1_id) - assert not api.InternalTest.efd_should_retry(m2_s2_t1_id), "Should not retry: session is faulty" api.InternalTest.mark_skip(m2_s2_t1_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t1_id), "Should not retry: session is faulty" api.InternalTest.start(m2_s2_t2_id) api.InternalTest.mark_pass(m2_s2_t2_id) assert not api.InternalTest.efd_should_retry(m2_s2_t2_id), "Should not retry: session is faulty" - api.InternalTest.mark_pass(m2_s2_t2_id) api.InternalTest.start(m2_s2_t3_id) - assert not api.InternalTest.efd_should_retry(m2_s2_t3_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m2_s2_t3_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t3_id), "Should not retry: session is faulty" api.InternalTest.start(m2_s2_t4_id) - assert not api.InternalTest.efd_should_retry(m2_s2_t4_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m2_s2_t4_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t4_id), "Should not retry: session is faulty" api.InternalTest.start(m2_s2_t5_id) - assert not api.InternalTest.efd_should_retry(m2_s2_t5_id), "Should not retry: session is faulty" api.InternalTest.mark_pass(m2_s2_t5_id) + assert not api.InternalTest.efd_should_retry(m2_s2_t5_id), "Should not retry: session is faulty" api.InternalTestSuite.finish(m2_s2_id) diff --git a/tests/ci_visibility/api/test_api_fake_runners.py b/tests/ci_visibility/api/test_api_fake_runners.py index 5067cfcfffe..a2ac51472f9 100644 --- a/tests/ci_visibility/api/test_api_fake_runners.py +++ b/tests/ci_visibility/api/test_api_fake_runners.py @@ -355,3 +355,47 @@ def test_manual_api_fake_efd_faulty_session(self): replace_os_env=True, ): subprocess.run(["python", "fake_runner_efd_faulty_session.py"]) + + @snapshot(ignores=SNAPSHOT_IGNORES) + def test_manual_api_fake_atr_mix_pass(self): + import fake_runner_atr_mix_pass + + fake_runner_src = inspect.getsource(fake_runner_atr_mix_pass) + self.testdir.makepyfile(fake_runner_atr_mix_pass=fake_runner_src) + self.testdir.chdir() + + with override_env( + _get_default_ci_env_vars( + dict( + DD_API_KEY="foobar.baz", + CI_PROJECT_DIR=str(self.testdir.tmpdir), + DD_CIVISIBILITY_AGENTLESS_ENABLED="false", + ), + mock_ci_env=True, + ), + replace_os_env=True, + ): + subprocess.run(["python", "fake_runner_atr_mix_pass.py"]) + + @snapshot(ignores=SNAPSHOT_IGNORES) + def test_manual_api_fake_atr_mix_fail(self): + import fake_runner_atr_mix_fail + + fake_runner_src = inspect.getsource(fake_runner_atr_mix_fail) + self.testdir.makepyfile(fake_runner_atr_mix_fail=fake_runner_src) + self.testdir.chdir() + + with override_env( + _get_default_ci_env_vars( + dict( + DD_API_KEY="foobar.baz", + CI_PROJECT_DIR=str(self.testdir.tmpdir), + DD_CIVISIBILITY_AGENTLESS_ENABLED="false", + DD_CIVISIBILITY_FLAKY_RETRY_COUNT="7", + DD_CIVISIBILITY_TOTAL_FLAKY_RETRY_COUNT="20", + ), + mock_ci_env=True, + ), + replace_os_env=True, + ): + subprocess.run(["python", "fake_runner_atr_mix_fail.py"]) diff --git a/tests/ci_visibility/test_atr.py b/tests/ci_visibility/test_atr.py new file mode 100644 index 00000000000..78925ba8a7e --- /dev/null +++ b/tests/ci_visibility/test_atr.py @@ -0,0 +1,197 @@ +from pathlib import Path +import typing as t +from unittest import mock + +import pytest + +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings +from ddtrace.internal.ci_visibility.api._base import TestVisibilitySessionSettings +from ddtrace.internal.ci_visibility.api._session import TestVisibilitySession +from ddtrace.internal.ci_visibility.api._test import TestVisibilityTest +from ddtrace.internal.ci_visibility.telemetry.constants import TEST_FRAMEWORKS +from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings +from tests.utils import DummyTracer + + +class TestCIVisibilityTestATR: + """Tests that the classes in the CIVisibility API correctly handle ATR + + Tests are done with EFD enabled and disabled to ensure that the classes handle both cases correctly. + """ + + def _get_session_settings( + self, atr_settings: AutoTestRetriesSettings, efd_enabled: bool = False + ) -> TestVisibilitySessionSettings: + return TestVisibilitySessionSettings( + tracer=DummyTracer(), + test_service="efd_test_service", + test_command="efd_test_command", + test_framework="efd_test_framework", + test_framework_metric_name=TEST_FRAMEWORKS.MANUAL, + test_framework_version="0.0", + session_operation_name="efd_session", + module_operation_name="efd_module", + suite_operation_name="efd_suite", + test_operation_name="efd_test", + workspace_path=Path().absolute(), + efd_settings=EarlyFlakeDetectionSettings(enabled=efd_enabled), + atr_settings=atr_settings, + ) + + @pytest.mark.parametrize( + "num_tests,atr_settings,atr_expected_retries", + ( + # Test defaults + # 8 tests should retry 5 times each for 40 total retries + (8, AutoTestRetriesSettings(enabled=True), [5] * 8), + # 200 tests should retry 5 times each for 1000 total retries + (200, AutoTestRetriesSettings(enabled=True), [5] * 200), + # 201 tests should retry 5 times each except for the last one + (201, AutoTestRetriesSettings(enabled=True), [5] * 200), + # Test custom settings + # Only the first test should retry, 10 times total + (3, AutoTestRetriesSettings(enabled=True, max_retries=100, max_session_total_retries=10), [10]), + # The first 8 tests should retry 2 times each, and the 9th should retry once + (20, AutoTestRetriesSettings(enabled=True, max_retries=2, max_session_total_retries=17), [2] * 8 + [1]), + # The first 5 tests should retry 7 times each, and the remaining 9 should retry 0 times + (13, AutoTestRetriesSettings(enabled=True, max_retries=7, max_session_total_retries=35), [7] * 5), + # Not-enabled should result in no retries + (8, AutoTestRetriesSettings(enabled=False), []), + ), + ) + @pytest.mark.parametrize("efd_enabled", (True, False)) + def test_atr_max_retries(self, num_tests, atr_settings, atr_expected_retries, efd_enabled): + """Tests that the Test class retries the expected number of times""" + + expected_total_retries_count = sum(atr_expected_retries) + expected_retried_tests_count = len(atr_expected_retries) + + total_retries = 0 + retried_tests = set() + session = TestVisibilitySession( + session_settings=self._get_session_settings(atr_settings, efd_enabled=efd_enabled) + ) + session.efd_is_faulty_session = lambda: False + + test_names = [f"atr_test_{i}" for i in range(num_tests)] + + for test_number, test_name in enumerate(test_names): + atr_test = TestVisibilityTest( + name=test_name, + session_settings=self._get_session_settings(atr_settings, efd_enabled=efd_enabled), + ) + + with mock.patch.object(atr_test, "get_session", return_value=session): + atr_test.start() + atr_test.finish_test(TestStatus.FAIL) + + retry_count = 0 + while atr_test.atr_should_retry(): # avoid infinite loops + retry_count += 1 + total_retries += 1 + retried_tests.add(atr_test) + + added_retry_num = atr_test.atr_add_retry(start_immediately=True) + assert added_retry_num == retry_count + atr_test.atr_finish_retry(added_retry_num, TestStatus.FAIL) + + assert retry_count == ( + atr_expected_retries[test_number] if test_number < len(atr_expected_retries) else 0 + ) + assert atr_test.atr_get_final_status() == TestStatus.FAIL + + assert total_retries == expected_total_retries_count + assert len(retried_tests) == expected_retried_tests_count + + @pytest.mark.parametrize( + "test_result,retry_results,expected_status", + ( + # Initial pass + ( + TestStatus.PASS, + (), + TestStatus.PASS, + ), + # Passes first retry + ( + TestStatus.FAIL, + (TestStatus.PASS,), + TestStatus.PASS, + ), + # Passes 5th retry + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), + TestStatus.PASS, + ), + # Never passes: + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL), + TestStatus.FAIL, + ), + # Passes 6th retry (so fails because we only do 5): + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.FAIL, TestStatus.PASS), + TestStatus.FAIL, + ), + # Skips initial attempt + ( + TestStatus.SKIP, + (), + TestStatus.SKIP, + ), + # Skips some retries and passes + ( + TestStatus.FAIL, + (TestStatus.FAIL, TestStatus.SKIP, TestStatus.PASS), + TestStatus.PASS, + ), + # Skips all retries + ( + TestStatus.FAIL, + (TestStatus.SKIP, TestStatus.SKIP, TestStatus.SKIP, TestStatus.SKIP, TestStatus.SKIP), + TestStatus.FAIL, + ), + # Skips some retries and fails + ( + TestStatus.FAIL, + (TestStatus.SKIP, TestStatus.SKIP, TestStatus.FAIL, TestStatus.FAIL, TestStatus.SKIP), + TestStatus.FAIL, + ), + ), + ) + def test_atr_final_status(self, test_result, retry_results: t.Iterable[TestStatus], expected_status): + """Tests that the EFD API correctly reports the final statuses of a test""" + atr_settings = AutoTestRetriesSettings(enabled=True) + session = TestVisibilitySession(session_settings=self._get_session_settings(atr_settings)) + session.efd_is_faulty_session = lambda: False + + atr_test = TestVisibilityTest( + name="atr_test", session_settings=self._get_session_settings(atr_settings, efd_enabled=True) + ) + + atr_test.get_session = lambda: session + + atr_test.start() + atr_test.finish_test(test_result) + expected_retry_number = 0 + for test_result in retry_results: + if not atr_test.atr_should_retry(): + break + expected_retry_number += 1 + added_retry_number = atr_test.atr_add_retry(start_immediately=True) + assert added_retry_number == expected_retry_number + atr_test.atr_finish_retry(added_retry_number, test_result) + assert atr_test.atr_get_final_status() == expected_status + + def test_atr_does_not_retry_if_disabled(self): + atr_test = TestVisibilityTest( + name="atr_test", + session_settings=self._get_session_settings(atr_settings=AutoTestRetriesSettings(enabled=False)), + ) + atr_test.start() + atr_test.finish_test(TestStatus.FAIL) + assert atr_test.atr_should_retry() is False diff --git a/tests/ci_visibility/test_efd.py b/tests/ci_visibility/test_efd.py index 7a564159433..c623e5db329 100644 --- a/tests/ci_visibility/test_efd.py +++ b/tests/ci_visibility/test_efd.py @@ -163,7 +163,7 @@ def test_efd_final_status(self, test_result, retry_results: t.Iterable[TestStatu for test_result in retry_results: expected_num_retry += 1 added_retry_number = efd_test.efd_add_retry(start_immediately=True) - assert added_retry_number + assert added_retry_number == expected_num_retry efd_test.efd_finish_retry(added_retry_number, test_result) assert efd_test.efd_get_final_status() == expected_statuses[0] assert efd_test.get_status() == expected_statuses[1] @@ -173,6 +173,8 @@ def test_efd_does_not_retry_if_disabled(self): name="efd_test", session_settings=self._get_session_settings(EarlyFlakeDetectionSettings(False)), ) + efd_test.start() + efd_test.finish_test(TestStatus.FAIL) assert efd_test.efd_should_retry() is False @pytest.mark.parametrize("faulty_session_threshold,expected_faulty", ((None, False), (10, True), (40, False))) diff --git a/tests/ci_visibility/util.py b/tests/ci_visibility/util.py index 7354033bb19..87d818191c5 100644 --- a/tests/ci_visibility/util.py +++ b/tests/ci_visibility/util.py @@ -40,7 +40,10 @@ def _get_default_civisibility_ddconfig(itr_skipping_level: ITR_SKIPPING_LEVEL = return new_ddconfig -def _fetch_unique_tests_side_effect(unique_test_ids: t.Set[InternalTestId]): +def _fetch_unique_tests_side_effect(unique_test_ids: t.Optional[t.Set[InternalTestId]] = None): + if unique_test_ids is None: + unique_test_ids = set() + def _side_effect(): CIVisibility._instance._unique_test_ids = unique_test_ids diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json new file mode 100644 index 00000000000..d2563710d41 --- /dev/null +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json @@ -0,0 +1,3006 @@ +[[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 70791, + "start": 1730218084512439542 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 71417, + "start": 1730218084528357708 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 50042, + "start": 1730218084528531125 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 46041, + "start": 1730218084528663917 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 43709, + "start": 1730218084528787458 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 5, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 41209, + "start": 1730218084528907333 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 6, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 51625, + "start": 1730218084529022417 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 7, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 43667, + "start": 1730218084529172500 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 8, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 32542, + "start": 1730218084529293625 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 9, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30833, + "start": 1730218084529397500 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 10, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30958, + "start": 1730218084529498625 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 11, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 28875, + "start": 1730218084529601625 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 12, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29208, + "start": 1730218084529698875 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 13, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30542, + "start": 1730218084529799500 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 14, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 28959, + "start": 1730218084529901083 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 15, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29959, + "start": 1730218084530000583 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t3", + "trace_id": 16, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.codeowners": "[\"@romain\", \"@romain2\"]", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t3", + "test.source.file": "my_file_1.py", + "test.status": "skip", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 12, + "test.source.start": 4 + }, + "duration": 56334, + "start": 1730218084530112708 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p1", + "trace_id": 17, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p1", + "test.parameters": "{\"param1\": \"value1\"}", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 31416, + "start": 1730218084530239167 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 18, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30417, + "start": 1730218084530336958 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 19, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29875, + "start": 1730218084530437917 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 20, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 31291, + "start": 1730218084530538417 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 21, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 28958, + "start": 1730218084530639792 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 22, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 33375, + "start": 1730218084530757958 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 23, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 31916, + "start": 1730218084530872292 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p3_id", + "trace_id": 24, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p3_id", + "test.parameters": "{\"param1\": \"value3\"}", + "test.status": "skip", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 32250, + "start": 1730218084530980958 + }], +[ + { + "name": "test_visibility.session", + "service": "test-test", + "resource": "test_visibility.session", + "trace_id": 25, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.status": "fail", + "test_session_id": "11513418841639438065", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 21548709, + "start": 1730218084511516958 + }, + { + "name": "test_visibility.module", + "service": "test-test", + "resource": "test_visibility.module", + "trace_id": 25, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.module": "m1", + "test.module_path": "", + "test.status": "fail", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 18848917, + "start": 1730218084512381083 + }, + { + "name": "test_visibility.suite", + "service": "test-test", + "resource": "test_visibility.suite", + "trace_id": 25, + "span_id": 4, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "14522272526427506506", + "test_session_id": "11513418841639438065", + "test_suite_id": "382174887352048625", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 18712917, + "start": 1730218084512410958 + }, + { + "name": "test_visibility.module", + "service": "test-test", + "resource": "test_visibility.module", + "trace_id": 25, + "span_id": 3, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.module": "m2", + "test.module_path": "", + "test.status": "fail", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1673209, + "start": 1730218084531287958 + }, + { + "name": "test_visibility.suite", + "service": "test-test", + "resource": "test_visibility.suite", + "trace_id": 25, + "span_id": 5, + "parent_id": 3, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 979917, + "start": 1730218084531311875 + }, + { + "name": "test_visibility.suite", + "service": "test-test", + "resource": "test_visibility.suite", + "trace_id": 25, + "span_id": 6, + "parent_id": 3, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.status": "fail", + "test.suite": "m2_s2", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "13125642143907159606", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 509083, + "start": 1730218084532359542 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t1", + "trace_id": 26, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t1", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 34833, + "start": 1730218084531333292 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 27, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29584, + "start": 1730218084531438458 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t3", + "trace_id": 28, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t3", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 31041, + "start": 1730218084531534792 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t4", + "trace_id": 29, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t4", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29750, + "start": 1730218084531634958 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t5", + "trace_id": 30, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t5", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 35250, + "start": 1730218084531749583 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t6", + "trace_id": 31, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t6", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30291, + "start": 1730218084531855292 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t7", + "trace_id": 32, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t7", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 28708, + "start": 1730218084531950292 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t8", + "trace_id": 33, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t8", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29959, + "start": 1730218084532053458 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t9", + "trace_id": 34, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t9", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "9902770138436951004", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 30625, + "start": 1730218084532149917 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s2_t1", + "trace_id": 35, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s2_t1", + "test.source.file": "my_file_1.py", + "test.status": "skip", + "test.suite": "m2_s2", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "13125642143907159606", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 49208, + "start": 1730218084532382792 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s2_t2", + "trace_id": 36, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s2_t2", + "test.status": "fail", + "test.suite": "m2_s2", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "13125642143907159606", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 29916, + "start": 1730218084532503917 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s2_t2", + "trace_id": 37, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s2_t2", + "test.status": "fail", + "test.suite": "m2_s2", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "13125642143907159606", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585 + }, + "duration": 32125, + "start": 1730218084532608375 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s2_t3", + "trace_id": 38, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721086400000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.codeowners": "[\"@romain\"]", + "test.command": "manual_atr_mix_fail", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s2_t3", + "test.source.file": "my_file_1.py", + "test.status": "pass", + "test.suite": "m2_s2", + "test_module_id": "1285008711190162976", + "test_session_id": "11513418841639438065", + "test_suite_id": "13125642143907159606", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45585, + "test.source.end": 12, + "test.source.start": 4 + }, + "duration": 49833, + "start": 1730218084532717292 + }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json new file mode 100644 index 00000000000..795e6c41e58 --- /dev/null +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json @@ -0,0 +1,1636 @@ +[[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 73083, + "start": 1730218099485156341 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 75709, + "start": 1730218099500880257 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 52333, + "start": 1730218099501069924 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 44208, + "start": 1730218099501203466 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t1", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 41583, + "start": 1730218099501321591 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 5, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 50291, + "start": 1730218099501440216 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 6, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 94375, + "start": 1730218099501569216 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t2", + "trace_id": 7, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t2", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 31417, + "start": 1730218099501739382 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t3", + "trace_id": 8, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.codeowners": "[\"@romain\", \"@romain2\"]", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t3", + "test.source.file": "my_file_1.py", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 12, + "test.source.start": 4 + }, + "duration": 88000, + "start": 1730218099501842007 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p1", + "trace_id": 9, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p1", + "test.parameters": "{\"param1\": \"value1\"}", + "test.status": "skip", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 45959, + "start": 1730218099502014257 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m1_s1_t4_p2_id", + "trace_id": 10, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.name": "m1_s1_t4_p2_id", + "test.parameters": "{\"param1\": \"value2\"}", + "test.status": "pass", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 30167, + "start": 1730218099502131507 + }], +[ + { + "name": "test_visibility.session", + "service": "test-test", + "resource": "test_visibility.session", + "trace_id": 11, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.status": "fail", + "test_session_id": "11758471677125334809", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 19127959, + "start": 1730218099484382632 + }, + { + "name": "test_visibility.module", + "service": "test-test", + "resource": "test_visibility.module", + "trace_id": 11, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.module": "m1", + "test.module_path": "", + "test.status": "fail", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 17240959, + "start": 1730218099485089382 + }, + { + "name": "test_visibility.suite", + "service": "test-test", + "resource": "test_visibility.suite", + "trace_id": 11, + "span_id": 4, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m1", + "test.module_path": "", + "test.status": "fail", + "test.suite": "m1_s1", + "test_module_id": "1063630063586883636", + "test_session_id": "11758471677125334809", + "test_suite_id": "4078044225450632605", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 17132834, + "start": 1730218099485118132 + }, + { + "name": "test_visibility.module", + "service": "test-test", + "resource": "test_visibility.module", + "trace_id": 11, + "span_id": 3, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.itr.tests_skipping.enabled": "false", + "test.module": "m2", + "test.module_path": "", + "test.status": "fail", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1032666, + "start": 1730218099502379466 + }, + { + "name": "test_visibility.suite", + "service": "test-test", + "resource": "test_visibility.suite", + "trace_id": 11, + "span_id": 5, + "parent_id": 3, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.status": "fail", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 923709, + "start": 1730218099502401132 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t1", + "trace_id": 12, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t1", + "test.source.file": "my_file_1.py", + "test.status": "skip", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 2, + "test.source.start": 1 + }, + "duration": 42791, + "start": 1730218099502420841 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 13, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "fail", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 28917, + "start": 1730218099502531007 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 14, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "skip", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 35292, + "start": 1730218099502643799 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 15, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "skip", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 30625, + "start": 1730218099502753049 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 16, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "skip", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 29500, + "start": 1730218099502854882 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 17, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "skip", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 28750, + "start": 1730218099502955424 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 18, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616 + }, + "duration": 31833, + "start": 1730218099503067924 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t3", + "trace_id": 19, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "6721087300000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.codeowners": "[\"@romain\"]", + "test.command": "manual_atr_mix_pass", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t3", + "test.source.file": "my_file_1.py", + "test.status": "pass", + "test.suite": "m2_s1", + "test_module_id": "6161152837526454920", + "test_session_id": "11758471677125334809", + "test_suite_id": "13026552725733508225", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 45616, + "test.source.end": 12, + "test.source.start": 4 + }, + "duration": 52042, + "start": 1730218099503189299 + }]] diff --git a/tests/utils.py b/tests/utils.py index fcf494075e4..01d8b9aa725 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1072,7 +1072,6 @@ def snapshot_context( elif r.status != 200: # The test agent returns nice error messages we can forward to the user. pytest.fail(to_unicode(r.read()), pytrace=False) - try: yield SnapshotTest( tracer=tracer, From 44ed02cee5d5823282287c534e78d093a10868be Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:06:20 -0400 Subject: [PATCH 084/372] chore: format release notes so the release script could parse (#11208) Release notes with `:` in them need to be entered as a block with `|`, otherwise the release script fails to parse it properly ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ...ity-fix_codeowners_including_comments-82d9cb733a2c7285.yaml | 3 ++- ...bility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml b/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml index 3fb4bb5abd8..95004e3d55e 100644 --- a/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml +++ b/releasenotes/notes/ci_visibility-fix_codeowners_including_comments-82d9cb733a2c7285.yaml @@ -1,3 +1,4 @@ --- fixes: - - CI Visibility: "fixes a bug where ``CODEOWNERS`` would incorrectly fail to discard line-level trailing comments (eg: ``@code/owner # my comment`` would result in codeowners being parsed as ``@code/owner``, ``#``, ``my``, and ``comment``)" + - | + CI Visibility: fixes a bug where ``CODEOWNERS`` would incorrectly fail to discard line-level trailing comments (eg: ``@code/owner # my comment`` would result in codeowners being parsed as ``@code/owner``, ``#``, ``my``, and ``comment``) diff --git a/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml b/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml index f248c2ad640..69d162acdac 100644 --- a/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml +++ b/releasenotes/notes/ci_visibility-fix_logged_errors_when_gitless-66a6cb3245314f3e.yaml @@ -1,4 +1,5 @@ --- fixes: - - CI Visibility: fixes unnecessary logging of an exception that would appear when trying to upload git metadata in + - | + CI Visibility: fixes unnecessary logging of an exception that would appear when trying to upload git metadata in an environment without functioning git (eg: missing ``git`` binary or ``.git`` directory) From 6b5dece002eece1fda62587492498a9b7fd48ceb Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:37:46 -0400 Subject: [PATCH 085/372] chore: update changelog for version 2.15.0 (#11185) - [x] update changelog for version 2.15.0 --- CHANGELOG.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f225cbf53bb..e96260ee2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,86 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.15.0 + +### New Features +- LLM Observability + - Introduces `prompt` and `name` arguments to `LLMObs.annotation_context` to support setting an integration generated span's name and `prompt` field. For more information on annotation contexts, see the docs [here](https://docs.datadoghq.com/llm_observability/setup/sdk/#annotating-a-span). + - langchain: Adds support for tracing `stream` calls on LCEL chains, chat completion models, or completion models. Note that due to an upstream issue with the `langchain` library itself, streamed responses will not be tagged correctly based on the choice index when the underlying model is configured to return `n>1` choices. Please refer to [this GitHub issue](https://github.com/langchain-ai/langchain/issues/26719) for more details. + - LangChain streamed calls (`llm.stream`, `chat_model.stream`, and `chain.stream`) submit to LLM Observability. + +- CI Visibility + - Adds the `test_session.name` tag to test events. The test session name can be set via the `DD_TEST_SESSION_NAME` environment variable. If `DD_TEST_SESSION_NAME` is not specified, the test session name is set from the CI job id and the test command. + +- Tracing + - Introduces Code Origin for Span, a new feature that allows collecting information about where entry and exit spans have been created in the user code . This feature is disabled by default and can be enabled by setting the `DD_CODE_ORIGIN_FOR_SPANS_ENABLED` environment variable to `true`. + - botocore: Adds span pointers for successful DynamoDB `DeleteItem` spans. + - botocore: Adds span pointers for successful DynamoDB `PutItem` spans. Table Primary Keys need to be provided with the `ddtrace.config.botocore.dynamodb_primary_key_names_for_tables` option or the `DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS` environment variable. + - botocore: Adds span pointers for successful DynamoDB `UpdateItem` spans. + - botocore: Adds span pointers for successful S3 `CompleteMultipartUpload` spans. + - botocore: Adds span pointers for successful S3 CopyObject spans. + - Adds `DD_TRACE_HTTP_CLIENT_ERROR_STATUSES` environment variable to configure the list of HTTP status codes that should be considered errors when instrumenting HTTP servers. + + +### Deprecation Notes +- Tracing + - The following attributes are now private and should not be accessed directly. The corresponding environment variables should be used instead. + - Use `DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING` instead of `ddtrace.config.http_tag_query_string` + - Use `DD_TRACE_HEADER_TAGS` instead of `ddtrace.config.trace_http_header_tags` + - Use `DD_TRACE_REPORT_HOSTNAME` instead of `ddtrace.config.report_hostname` + - Use `DD_TRACE_HEALTH_METRICS_ENABLED` instead of `ddtrace.config.health_metrics_enabled` + - Use `DD_TRACE_ANALYTICS_ENABLED` instead of `ddtrace.config.analytics_enabled` + - Use `DD_TRACE_CLIENT_IP_HEADER` instead of `ddtrace.config.client_ip_header` + - Use `DD_TRACE_CLIENT_IP_ENABLED` instead of `ddtrace.config.retrieve_client_ip` + - Use `DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED` instead of `ddtrace.config.propagation_http_baggage_enabled` + - Set `DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP` to an empty string instead of setting `ddtrace.config.global_query_string_obfuscation_disabled` to True (default value is False) + - Use `DD_TRACE_METHODS` instead of `ddtrace.config.trace_methods` + - Use `DD_CIVISIBILITY_LOG_LEVEL` instead of `ddtrace.config.ci_visibility_log_level` + - Deprecates the `DD_TRACE_SAMPLE_RATE` environment variable. It will be removed in 3.0.0. Use `DD_TRACE_SAMPLING_RULES` to configure sampling rates instead. + - `DD_TRACE_API_VERSION=v0.3` is deprecated. Use `v0.4` or `v0.5` instead. + + +### Bug Fixes +- Code security + - Resolves an issue where partial matches on function names we aimed to patch were being patched instead of full matches on them. + - Always report a telemetry log error when an IAST propagation error raises, regardless of whether the `_DD_IAST_DEBUG` environment variable is enabled or not. + - Ensures that only the IAST propagation context is cleared instead of all contexts, which could otherwise cause propagation loss in multithreaded applications. Additionally, it improves validations in both the Processor and Vulnerability Reporter, depending on whether IAST is active or not. + - Ensures IAST propagation does not raise side effects related to `re.finditer`. + +- LLM Observability + - Resolves an issue where `LLMObs.enable()` did not patch google_generativeai library. + - botocore: Fixes the bedrock model and model provider interpretation from `modelId` when using cross-region inference. + - Resolves an issue where LLM Observability evaluation metrics were not being submitted in forked processes. The evaluation metric writer thread now automatically restarts when a forked process is detected. + - The OpenAI, LangChain, Anthropic, Bedrock, and Gemini integrations now will handle and log errors during LLM Observability span processing to avoid disrupting user applications. + +- Profiling + - Improves the error message when the native exporter fails to load and stops profiling from starting if ddtrace is also being injected. + - All files with platform-dependent code have had their filenames updated to reflect the platform they are for. This fixes issues where the wrong file would be used on a given platform. + - Fixes endpoint profiling for stack v2, when `DD_PROFILING_STACK_V2_ENABLED` is set. + - Fixes endpoint profiling when using libdatadog exporter, either with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + - Enables code provenance when using libdatadog exporter with `DD_PROFILING_EXPORT_LIBDD_ENABLED`, `DD_PROFILING_STACK_V2_ENABLED`, or `DD_PROFILING_TIMELINE_ENABLED`. + - Fixes an issue where stack v2 couldn't be enabled as pthread was not properly linked on some debian based images for aarch64 architecture. + - Fixes an issue where the flame graph was upside down for stack v2 with `DD_PROFILING_STACK_V2_ENABLED`. + +- Tracing + - elasticsearch: Resolves an issue where span tags were not fully populated on "sampled" spans, causing metric dimensions to be incorrect when spans were prematurely marked as sampled, including `resource_name`. + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + - celery: Fixes an issue where `celery.apply` spans didn't close if the `after_task_publish` or `task_postrun` signals didn't get sent when using `apply_async`, which can happen if there is an internal exception during the handling of the task. This update also marks the span as an error if an exception occurs. + - celery: Fixes an issue where `celery.apply` spans using task_protocol 1 didn't close by improving the check for the task id in the body. + - Removes a reference cycle that caused unnecessary garbage collection for top-level spans. + - Ensures that `http.url` span tag contains the full query string when `DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP` is set to an empty string. + - Ensures `DD_TRACE_RATE_LIMIT` environment variable is only applied to spans for which tracer sampling is configured. For spans not matching sampling rules default rate limits should be applied by the Datadog Agent. + +- Other + - Updates import path in FastAPI module to use the new ASGI module location. + +### Other Changes +- Code Security + - Update default security rules to 1.13.1. This enable Exploit Prevention powered by RASP for LFI and Command Injection by default when ASM is enabled. + + --- ## 2.14.4 From f3b5275b8a6be8d3b7c8ab1e4e40ed96cb9aa386 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Wed, 30 Oct 2024 10:52:49 -0400 Subject: [PATCH 086/372] fix(pymongo): add pin type check (#11145) Small type check fix for expected `Pin` type to unblock #11041 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/pymongo/client.py | 5 ++++- releasenotes/notes/pymongo-check-pin-c3cb9d1fbdc82fb3.yaml | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/pymongo-check-pin-c3cb9d1fbdc82fb3.yaml diff --git a/ddtrace/contrib/internal/pymongo/client.py b/ddtrace/contrib/internal/pymongo/client.py index cb7466ac7a8..d5b2530d1f7 100644 --- a/ddtrace/contrib/internal/pymongo/client.py +++ b/ddtrace/contrib/internal/pymongo/client.py @@ -103,7 +103,10 @@ def _trace_topology_select_server(func, args, kwargs): # Ensure the pin used on the traced mongo client is passed down to the topology instance # This allows us to pass the same pin in traced server objects. topology_instance = get_argument_value(args, kwargs, 0, "self") - ddtrace.Pin.get_from(topology_instance).onto(server) + pin = ddtrace.Pin.get_from(topology_instance) + + if pin is not None: + pin.onto(server) return server diff --git a/releasenotes/notes/pymongo-check-pin-c3cb9d1fbdc82fb3.yaml b/releasenotes/notes/pymongo-check-pin-c3cb9d1fbdc82fb3.yaml new file mode 100644 index 00000000000..1f1ad12a669 --- /dev/null +++ b/releasenotes/notes/pymongo-check-pin-c3cb9d1fbdc82fb3.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + pymongo: add type checking to solve an issue where ``NoneType`` instead of expected ``Pin`` object would throw an error in ``TracedTopology`` method. From bbbf0491830283cc3a0e98b3833384bd6bf29bc5 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 30 Oct 2024 15:17:22 +0000 Subject: [PATCH 087/372] chore(di): fix subclass test (#11207) We fix a regression introduced in #11153 whereby the wrong builtin method was being used to check if a type is a subclass of another type. This caused system tests to fail with function probes failing to instrument due to the missing argument set by the faulty branching. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_probe/remoteconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/debugging/_probe/remoteconfig.py b/ddtrace/debugging/_probe/remoteconfig.py index 763c6516db5..b305e0fb5da 100644 --- a/ddtrace/debugging/_probe/remoteconfig.py +++ b/ddtrace/debugging/_probe/remoteconfig.py @@ -104,7 +104,7 @@ def build(cls, args: Dict[str, Any], attribs: Dict[str, Any]) -> Any: args["module"] = where.get("type") or where["typeName"] args["func_qname"] = where.get("method") or where["methodName"] - if isinstance(cls.__function_class__, TimingMixin): + if issubclass(cls.__function_class__, TimingMixin): args["evaluate_at"] = ProbeEvalTiming[attribs.get("evaluateAt", "DEFAULT")] return cls.__function_class__(**args) @@ -242,7 +242,7 @@ def get_probes(config: dict, status_logger: ProbeStatusLogger) -> Iterable[Probe raise except Exception as e: status_logger.error( - probe=Probe(probe_id=config["id"], version=config["version"], tags={}), + probe=Probe(probe_id=config["id"], version=config.get("version", 0), tags={}), error=(type(e).__name__, str(e)), ) return [] From 576192493e365d273023e83d1e33153a04391189 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:17:39 -0400 Subject: [PATCH 088/372] feat(llmobs): adjust langchain llm spans coloring logic and token metrics counts (#11182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What does this PR do This PR accomplishes two task: 1. Adds token metrics to langchain `chat_model` and `llm` operations that are already marked as `llm`/non-workflow spans, such that not only the total number of LLM calls are computed correctly for metrics, but token counts are as well. This PR accomplishes this by attempting to grab token metrics from various places it could be, given that it is provider and version-dependant. 2. Re-colors langchain-openai chat model spans that were started as a result of `response_format={"type": "json_object"}` being passed in as an argument to `chat_model.(a)invoke`. This could happen manually, or as a result of binding the chat model with `with_structured_output(SomePydanticModel, method="json_mode")`. This is because LangChain uses the beta OpenAI client and a `parse` method, both of which we don't trace, so we never generate an OpenAI `llm` span. Thus, we mark the langchain chat model span as an `llm` span instead of a `workflow` span, and attempt to attach token metrics, to give proper metrics counts on number of LLM calls and token metrics in the LLM Observability UI and metrics. ## Manual Testing ### Using `with_structured_output(..., method="json_mode") #### Before this change Screenshot 2024-10-25 at 3 06 17 PM #### After this change Screenshot 2024-10-25 at 3 07 14 PM ### Enabling and Disabling the OpenAI integration #### Enabled Screenshot 2024-10-25 at 3 08 47 PM #### Disabled Screenshot 2024-10-25 at 3 09 03 PM ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> --- ddtrace/llmobs/_integrations/langchain.py | 96 +++++++++++++++-- ...metrics-and-coloring-21eed854130b235c.yaml | 5 + .../langchain/test_langchain_llmobs.py | 101 ++++++++++++++---- 3 files changed, 177 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/llmobs-langchain-token-metrics-and-coloring-21eed854130b235c.yaml diff --git a/ddtrace/llmobs/_integrations/langchain.py b/ddtrace/llmobs/_integrations/langchain.py index 7f7075e874e..c2304289c2c 100644 --- a/ddtrace/llmobs/_integrations/langchain.py +++ b/ddtrace/llmobs/_integrations/langchain.py @@ -1,3 +1,4 @@ +from collections import defaultdict import json from typing import Any from typing import Dict @@ -14,6 +15,7 @@ from ddtrace.llmobs import LLMObs from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES +from ddtrace.llmobs._constants import INPUT_TOKENS_METRIC_KEY from ddtrace.llmobs._constants import INPUT_VALUE from ddtrace.llmobs._constants import METADATA from ddtrace.llmobs._constants import METRICS @@ -21,8 +23,10 @@ from ddtrace.llmobs._constants import MODEL_PROVIDER from ddtrace.llmobs._constants import OUTPUT_DOCUMENTS from ddtrace.llmobs._constants import OUTPUT_MESSAGES +from ddtrace.llmobs._constants import OUTPUT_TOKENS_METRIC_KEY from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import SPAN_KIND +from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.utils import Document @@ -73,8 +77,8 @@ def _llmobs_set_tags( is_workflow = False + llmobs_integration = "custom" if model_provider: - llmobs_integration = "custom" if model_provider.startswith(BEDROCK_PROVIDER_NAME): llmobs_integration = "bedrock" elif model_provider.startswith(OPENAI_PROVIDER_NAME): @@ -85,9 +89,11 @@ def _llmobs_set_tags( is_workflow = LLMObs._integration_is_enabled(llmobs_integration) if operation == "llm": - self._llmobs_set_meta_tags_from_llm(span, args, kwargs, response, is_workflow=is_workflow) + self._llmobs_set_tags_from_llm(span, args, kwargs, response, is_workflow=is_workflow) elif operation == "chat": - self._llmobs_set_meta_tags_from_chat_model(span, args, kwargs, response, is_workflow=is_workflow) + # langchain-openai will call a beta client "response_format" is passed in the kwargs, which we do not trace + is_workflow = is_workflow and not (llmobs_integration == "openai" and ("response_format" in kwargs)) + self._llmobs_set_tags_from_chat_model(span, args, kwargs, response, is_workflow=is_workflow) elif operation == "chain": self._llmobs_set_meta_tags_from_chain(span, args, kwargs, outputs=response) elif operation == "embedding": @@ -97,8 +103,6 @@ def _llmobs_set_tags( elif operation == "tool": self._llmobs_set_meta_tags_from_tool(span, tool_inputs=kwargs, tool_output=response) - span.set_tag_str(METRICS, safe_json({})) - def _llmobs_set_metadata(self, span: Span, model_provider: Optional[str] = None) -> None: if not model_provider: return @@ -120,7 +124,7 @@ def _llmobs_set_metadata(self, span: Span, model_provider: Optional[str] = None) if metadata: span.set_tag_str(METADATA, safe_json(metadata)) - def _llmobs_set_meta_tags_from_llm( + def _llmobs_set_tags_from_llm( self, span: Span, args: List[Any], kwargs: Dict[str, Any], completions: Any, is_workflow: bool = False ) -> None: span.set_tag_str(SPAN_KIND, "workflow" if is_workflow else "llm") @@ -148,9 +152,19 @@ def _llmobs_set_meta_tags_from_llm( message_content = [{"content": completions}] # single completion for streams else: message_content = [{"content": completion[0].text} for completion in completions.generations] + + if not is_workflow: + input_tokens, output_tokens, total_tokens = self.check_token_usage_chat_or_llm_result(completions) + if total_tokens > 0: + metrics = { + INPUT_TOKENS_METRIC_KEY: input_tokens, + OUTPUT_TOKENS_METRIC_KEY: output_tokens, + TOTAL_TOKENS_METRIC_KEY: total_tokens, + } + span.set_tag_str(METRICS, safe_json(metrics)) span.set_tag_str(output_tag_key, safe_json(message_content)) - def _llmobs_set_meta_tags_from_chat_model( + def _llmobs_set_tags_from_chat_model( self, span: Span, args: List[Any], @@ -194,6 +208,14 @@ def _llmobs_set_meta_tags_from_chat_model( span.set_tag_str(output_tag_key, safe_json([{"content": content, "role": ROLE_MAPPING.get(role, "")}])) return + input_tokens, output_tokens, total_tokens = 0, 0, 0 + tokens_set_top_level = False + if not is_workflow: + # tokens are usually set at the top-level ChatResult or LLMResult object + input_tokens, output_tokens, total_tokens = self.check_token_usage_chat_or_llm_result(chat_completions) + tokens_set_top_level = total_tokens > 0 + + tokens_per_choice_run_id: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int)) for message_set in chat_completions.generations: for chat_completion in message_set: chat_completion_msg = chat_completion.message @@ -203,8 +225,32 @@ def _llmobs_set_meta_tags_from_chat_model( if tool_calls_info: output_message["tool_calls"] = tool_calls_info output_messages.append(output_message) + + # if it wasn't set above, check for token usage on the AI message object level + # these blocks contain the same count as what would be set at the top level. + # do not append to the count, just set it once + if not is_workflow and not tokens_set_top_level: + tokens, run_id = self.check_token_usage_ai_message(chat_completion_msg) + input_tokens, output_tokens, total_tokens = tokens + tokens_per_choice_run_id[run_id]["input_tokens"] = input_tokens + tokens_per_choice_run_id[run_id]["output_tokens"] = output_tokens + tokens_per_choice_run_id[run_id]["total_tokens"] = total_tokens + + if not is_workflow and not tokens_set_top_level: + input_tokens = sum(v["input_tokens"] for v in tokens_per_choice_run_id.values()) + output_tokens = sum(v["output_tokens"] for v in tokens_per_choice_run_id.values()) + total_tokens = sum(v["total_tokens"] for v in tokens_per_choice_run_id.values()) + span.set_tag_str(output_tag_key, safe_json(output_messages)) + if not is_workflow and total_tokens > 0: + metrics = { + INPUT_TOKENS_METRIC_KEY: input_tokens, + OUTPUT_TOKENS_METRIC_KEY: output_tokens, + TOTAL_TOKENS_METRIC_KEY: total_tokens, + } + span.set_tag_str(METRICS, safe_json(metrics)) + def _extract_tool_calls(self, chat_completion_msg: Any) -> List[Dict[str, Any]]: """Extracts tool calls from a langchain chat completion.""" tool_calls = getattr(chat_completion_msg, "tool_calls", None) @@ -441,6 +487,42 @@ def record_usage(self, span: Span, usage: Dict[str, Any]) -> None: if total_cost: self.metric(span, "incr", "tokens.total_cost", total_cost) + def check_token_usage_chat_or_llm_result(self, result): + """Checks for token usage on the top-level ChatResult or LLMResult object""" + llm_output = getattr(result, "llm_output", {}) + if llm_output is None: # in case it is explicitly set to None + return 0, 0, 0 + + token_usage = llm_output.get("token_usage", llm_output.get("usage_metadata", llm_output.get("usage", {}))) + if token_usage is None or not isinstance(token_usage, dict): # in case it is explicitly set to None + return 0, 0, 0 + + # could either be "{prompt,completion}_tokens" or "{input,output}_tokens" + input_tokens = token_usage.get("prompt_tokens", 0) or token_usage.get("input_tokens", 0) or 0 + output_tokens = token_usage.get("completion_tokens", 0) or token_usage.get("output_tokens", 0) or 0 + total_tokens = token_usage.get("total_tokens", input_tokens + output_tokens) or 0 + + return input_tokens, output_tokens, total_tokens + + def check_token_usage_ai_message(self, ai_message): + """Checks for token usage on an AI message object""" + # depending on the provider + langchain-core version, the usage metadata can be in different places + # either chat_completion_msg.usage_metadata or chat_completion_msg.response_metadata.{token}_usage + usage = getattr(ai_message, "usage_metadata", None) + + run_id = getattr(ai_message, "id", None) or getattr(ai_message, "run_id", "") + run_id_base = "-".join(run_id.split("-")[:-1]) if run_id else "" + + response_metadata = getattr(ai_message, "response_metadata", {}) or {} + usage = usage or response_metadata.get("usage", {}) or response_metadata.get("token_usage", {}) + + # could either be "{prompt,completion}_tokens" or "{input,output}_tokens" + input_tokens = usage.get("input_tokens", 0) or usage.get("prompt_tokens", 0) + output_tokens = usage.get("output_tokens", 0) or usage.get("completion_tokens", 0) + total_tokens = usage.get("total_tokens", input_tokens + output_tokens) + + return (input_tokens, output_tokens, total_tokens), run_id_base + def format_io( self, messages, diff --git a/releasenotes/notes/llmobs-langchain-token-metrics-and-coloring-21eed854130b235c.yaml b/releasenotes/notes/llmobs-langchain-token-metrics-and-coloring-21eed854130b235c.yaml new file mode 100644 index 00000000000..75b8688dd91 --- /dev/null +++ b/releasenotes/notes/llmobs-langchain-token-metrics-and-coloring-21eed854130b235c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + LLM Observability: When not using a provider integration (OpenAI, Anthropic, or Bedrock) with the LangChain integration, token metrics will be appended to the LLM Observability ``llm`` span. + LLM Observability: When langchain's ``chat_model.with_structured_output(..., method="json_mode")`` is used, or ``response_format={"type": "json_object"}`` is passed into a langchain chat model invocation, the LLM Observability span will be an ``llm`` span instead of a ``workflow`` span. diff --git a/tests/contrib/langchain/test_langchain_llmobs.py b/tests/contrib/langchain/test_langchain_llmobs.py index 450e7402d85..6e9d4b4f1f8 100644 --- a/tests/contrib/langchain/test_langchain_llmobs.py +++ b/tests/contrib/langchain/test_langchain_llmobs.py @@ -34,7 +34,9 @@ from langchain_core.messages import HumanMessage -def _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role=None, mock_io=False): +def _assert_expected_llmobs_llm_span( + span, mock_llmobs_span_writer, input_role=None, mock_io=False, mock_token_metrics=False +): provider = span.get_tag("langchain.request.provider") metadata = {} @@ -59,6 +61,10 @@ def _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role=N input_messages[0]["role"] = input_role output_messages[0]["role"] = "assistant" + metrics = ( + {"input_tokens": mock.ANY, "output_tokens": mock.ANY, "total_tokens": mock.ANY} if mock_token_metrics else {} + ) + mock_llmobs_span_writer.enqueue.assert_any_call( _expected_llmobs_llm_span_event( span, @@ -67,7 +73,7 @@ def _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role=N input_messages=input_messages if not mock_io else mock.ANY, output_messages=output_messages if not mock_io else mock.ANY, metadata=metadata, - token_metrics={}, + token_metrics=metrics, tags={"ml_app": "langchain_test"}, ) ) @@ -205,7 +211,11 @@ def test_llmobs_openai_llm(self, langchain, mock_llmobs_span_writer, mock_tracer cassette_name="openai_completion_sync.yaml", ) assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + span, + mock_llmobs_span_writer, + mock_token_metrics=True, + ) def test_llmobs_cohere_llm(self, langchain, mock_llmobs_span_writer, mock_tracer): span = self._invoke_llm( @@ -254,7 +264,7 @@ def test_llmobs_openai_chat_model(self, langchain, mock_llmobs_span_writer, mock cassette_name="openai_chat_completion_sync_call.yaml", ) assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role="user") + _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role="user", mock_token_metrics=True) @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") def test_llmobs_chain(self, langchain, mock_llmobs_span_writer, mock_tracer): @@ -289,7 +299,11 @@ def test_llmobs_chain(self, langchain, mock_llmobs_span_writer, mock_tracer): } ), ) - _assert_expected_llmobs_llm_span(trace[2], mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + trace[2], + mock_llmobs_span_writer, + mock_token_metrics=True, + ) @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") def test_llmobs_chain_nested(self, langchain, mock_llmobs_span_writer, mock_tracer): @@ -326,9 +340,17 @@ def test_llmobs_chain_nested(self, langchain, mock_llmobs_span_writer, mock_trac mock_llmobs_span_writer, input_value=json.dumps({"input_text": input_text}), ) - _assert_expected_llmobs_llm_span(trace[2], mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + trace[2], + mock_llmobs_span_writer, + mock_token_metrics=True, + ) _assert_expected_llmobs_chain_span(trace[3], mock_llmobs_span_writer) - _assert_expected_llmobs_llm_span(trace[4], mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + trace[4], + mock_llmobs_span_writer, + mock_token_metrics=True, + ) @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") def test_llmobs_chain_schema_io(self, langchain, mock_llmobs_span_writer, mock_tracer): @@ -377,7 +399,7 @@ def test_llmobs_chain_schema_io(self, langchain, mock_llmobs_span_writer, mock_t } ), ) - _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer, mock_io=True) + _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer, mock_io=True, mock_token_metrics=True) def test_llmobs_embedding_query(self, langchain, mock_llmobs_span_writer, mock_tracer): embedding_model = langchain.embeddings.OpenAIEmbeddings() @@ -468,7 +490,11 @@ def test_llmobs_openai_llm(self, langchain_openai, mock_llmobs_span_writer, mock cassette_name="openai_completion_sync.yaml", ) assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + span, + mock_llmobs_span_writer, + mock_token_metrics=True, + ) def test_llmobs_cohere_llm(self, langchain_community, mock_llmobs_span_writer, mock_tracer): if langchain_community is None: @@ -504,7 +530,12 @@ def test_llmobs_openai_chat_model(self, langchain_openai, mock_llmobs_span_write role="user", ) assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role="user") + _assert_expected_llmobs_llm_span( + span, + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) def test_llmobs_chain(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( @@ -534,7 +565,11 @@ def test_llmobs_chain(self, langchain_core, langchain_openai, mock_llmobs_span_w input_value=json.dumps([{"input": "Can you explain what an LLM chain is?"}]), output_value=expected_output, ) - _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer) + _assert_expected_llmobs_llm_span( + trace[1], + mock_llmobs_span_writer, + mock_token_metrics=True, + ) def test_llmobs_chain_nested(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): prompt1 = langchain_core.prompts.ChatPromptTemplate.from_template("what is the city {person} is from?") @@ -564,8 +599,18 @@ def test_llmobs_chain_nested(self, langchain_core, langchain_openai, mock_llmobs input_value=json.dumps([{"person": "Spongebob Squarepants", "language": "Spanish"}]), output_value=mock.ANY, ) - _assert_expected_llmobs_llm_span(trace[2], mock_llmobs_span_writer, input_role="user") - _assert_expected_llmobs_llm_span(trace[3], mock_llmobs_span_writer, input_role="user") + _assert_expected_llmobs_llm_span( + trace[2], + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) + _assert_expected_llmobs_llm_span( + trace[3], + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) @flaky(1735812000, reason="batch() is non-deterministic in which order it processes inputs") @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Python <3.11 required") @@ -589,8 +634,18 @@ def test_llmobs_chain_batch(self, langchain_core, langchain_openai, mock_llmobs_ input_value=json.dumps(["chickens", "pigs"]), output_value=mock.ANY, ) - _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer, input_role="user") - _assert_expected_llmobs_llm_span(trace[2], mock_llmobs_span_writer, input_role="user") + _assert_expected_llmobs_llm_span( + trace[1], + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) + _assert_expected_llmobs_llm_span( + trace[2], + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) def test_llmobs_chain_schema_io(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( @@ -626,7 +681,12 @@ def test_llmobs_chain_schema_io(self, langchain_core, langchain_openai, mock_llm ), output_value=json.dumps(["assistant", "Mitochondria."]), ) - _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer, mock_io=True) + _assert_expected_llmobs_llm_span( + trace[1], + mock_llmobs_span_writer, + mock_io=True, + mock_token_metrics=True, + ) def test_llmobs_anthropic_chat_model(self, langchain_anthropic, mock_llmobs_span_writer, mock_tracer): chat = langchain_anthropic.ChatAnthropic(temperature=0, model="claude-3-opus-20240229", max_tokens=15) @@ -637,7 +697,12 @@ def test_llmobs_anthropic_chat_model(self, langchain_anthropic, mock_llmobs_span cassette_name="anthropic_chat_completion_sync.yaml", ) assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role="user") + _assert_expected_llmobs_llm_span( + span, + mock_llmobs_span_writer, + input_role="user", + mock_token_metrics=True, + ) def test_llmobs_embedding_query(self, langchain_community, langchain_openai, mock_llmobs_span_writer, mock_tracer): if langchain_openai is None: @@ -762,7 +827,7 @@ def add(a: int, b: int) -> int: } ], metadata={"temperature": 0.7}, - token_metrics={}, + token_metrics={"input_tokens": mock.ANY, "output_tokens": mock.ANY, "total_tokens": mock.ANY}, tags={"ml_app": "langchain_test"}, ) ) From 237874027de8c369d798016fad7167b11aa07cbf Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:21:00 -0400 Subject: [PATCH 089/372] chore(llmobs): implement non skeleton code for ragas faithfulness (#10795) This PR adds in the non-boiler plate code for the ragas faithfulness evaluator. The majority of LOC changes are from cassettes/requirements. The main logic is in `ddtrace/llmobs/_evaluators/ragas/faithfulness.py`. There are four important features of this PR: ### 1 .Extracting the inputs to a ragas faithfulness eval from a span A span event must contain data necessary for ragas evaluations - question, context, and answer. The evaluator tries to extract this data by looking at the span event using the following logic: ``` question = input.prompt.variables.question OR input.messages[-1].content context = input.prompt.variables.context answer = output.messages[-1].content ``` **Relevant tests...** `test_ragas_faithfulness_submits_evaluation...` `test_ragas_faithfulness_returns_none_if_inputs_extraction_fails` ### 2. Ragas faithfulness implementation See the [evaluate](https://github.com/DataDog/dd-trace-py/blob/a26b4ab514db35d6135bef510be13dc3d8dc4d52/ddtrace/llmobs/_evaluators/ragas/faithfulness.py#L110) function for the underlying Ragas faithfulness implementation. It roughly follows the [original](https://github.com/explodinggradients/ragas/blob/89115bfc9250d5335d8f8ab328f065021740d931/src/ragas/metrics/_faithfulness.py#L278C1-L279C1) source implementation in the ragas framework. **Relevant tests...** `test_ragas_faithfulness_submits_evaluation...` ### 3. Tracing RAGAS Tracing RAGAS is a requirement for faithfulness a user's ml app will be polluted by a bunch of auto-instrumented langchain spans. - The `ml_app` of ragas traces should be `dd-ragas-{original ml app name}`. - All ragas traces are marked with an `runner.integration:ragas` tag. This tells us that these traces are evaluations traces from the ragas integration. We can tell it's a ragas span by looking at the `ml app` of the span at trace processing time. We also use this to safegaurd against infinite eval loops (enqueuing an llm span generated from an evaluation to the evaluator runner). **Relevant tests...** `test_ragas_faithfulness_emits_traces` `test_llmobs_with_evaluator_runner_does_not_enqueue_evaluation_spans` ### 4. RAGAS Evaluator Setup - Ragas dependencies (ragas, langchain) are only required if the ragas faithfulness evaluator is configured. - The ragas evaluator should also always use the most up to date faithfulness instance from the ragas library itself to allow a user to customize the llm's & prompts for faithfulness. - If an llm is not set by the user, we use the default llm given to us by ragas's `llm_factory` method **Relevant tests...** `test_ragas_faithfulness_disabled_if_dependencies_not_present` `test_ragas_evaluator_init` `test_ragas_faithfulness_has_modified_faithfulness_instance` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan Co-authored-by: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Co-authored-by: kyle --- .riot/requirements/12bb9e4.txt | 27 -- .riot/requirements/12c5529.txt | 86 ++++ .riot/requirements/146f2d8.txt | 86 ++++ .riot/requirements/16562eb.txt | 32 -- .riot/requirements/1687eab.txt | 83 ++++ .riot/requirements/184f390.txt | 28 -- .riot/requirements/1b9d9b7.txt | 25 -- .riot/requirements/1d0bd1e.txt | 28 -- .riot/requirements/4102ef5.txt | 83 ++++ .riot/requirements/771848b.txt | 86 ++++ .riot/requirements/bb8bd85.txt | 25 -- ddtrace/llmobs/_constants.py | 8 + .../llmobs/_evaluators/ragas/faithfulness.py | 387 +++++++++++++++++- ddtrace/llmobs/_evaluators/ragas/models.py | 36 ++ ddtrace/llmobs/_evaluators/runner.py | 72 ++-- ddtrace/llmobs/_evaluators/sampler.py | 18 + ddtrace/llmobs/_llmobs.py | 15 +- ddtrace/llmobs/_trace_processor.py | 29 +- riotfile.py | 3 + tests/llmobs/_utils.py | 168 +++++++- tests/llmobs/conftest.py | 60 ++- ...bs_evaluator_runner.send_score_metric.yaml | 8 +- ..._runner_periodic_enqueues_eval_metric.yaml | 271 ++++++++++++ ...tor_runner_timed_enqueues_eval_metric.yaml | 270 ++++++++++++ ....emits_traces_and_evaluations_on_exit.yaml | 315 ++++++++++++++ ....test_ragas_faithfulness_emits_traces.yaml | 271 ++++++++++++ ...ragas_faithfulness_submits_evaluation.yaml | 275 +++++++++++++ ...ion_on_span_with_question_in_messages.yaml | 272 ++++++++++++ tests/llmobs/test_llmobs_evaluator_runner.py | 15 +- ...est_llmobs_ragas_faithfulness_evaluator.py | 200 +++++++++ tests/llmobs/test_llmobs_service.py | 33 ++ tests/llmobs/test_llmobs_trace_processor.py | 48 ++- 32 files changed, 3107 insertions(+), 256 deletions(-) delete mode 100644 .riot/requirements/12bb9e4.txt create mode 100644 .riot/requirements/12c5529.txt create mode 100644 .riot/requirements/146f2d8.txt delete mode 100644 .riot/requirements/16562eb.txt create mode 100644 .riot/requirements/1687eab.txt delete mode 100644 .riot/requirements/184f390.txt delete mode 100644 .riot/requirements/1b9d9b7.txt delete mode 100644 .riot/requirements/1d0bd1e.txt create mode 100644 .riot/requirements/4102ef5.txt create mode 100644 .riot/requirements/771848b.txt delete mode 100644 .riot/requirements/bb8bd85.txt create mode 100644 ddtrace/llmobs/_evaluators/ragas/models.py create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_periodic_enqueues_eval_metric.yaml create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_timed_enqueues_eval_metric.yaml create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.emits_traces_and_evaluations_on_exit.yaml create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_emits_traces.yaml create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation.yaml create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages.yaml create mode 100644 tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py diff --git a/.riot/requirements/12bb9e4.txt b/.riot/requirements/12bb9e4.txt deleted file mode 100644 index 8fa3b4d92ca..00000000000 --- a/.riot/requirements/12bb9e4.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/12bb9e4.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/12c5529.txt b/.riot/requirements/12c5529.txt new file mode 100644 index 00000000000..10b6e8ecd4a --- /dev/null +++ b/.riot/requirements/12c5529.txt @@ -0,0 +1,86 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/12c5529.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.2.post1 +appdirs==1.4.4 +async-timeout==4.0.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +dataclasses-json==0.6.7 +datasets==3.0.1 +dill==0.3.8 +distro==1.9.0 +exceptiongroup==1.2.2 +filelock==3.16.1 +frozenlist==1.4.1 +fsspec[http]==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.26.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.6.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.16 +langchain-community==0.2.17 +langchain-core==0.2.41 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.136 +marshmallow==3.23.0 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.26.4 +openai==1.52.0 +opentracing==2.4.0 +orjson==3.10.7 +packaging==24.1 +pandas==2.2.3 +pluggy==1.5.0 +propcache==0.2.0 +pyarrow==17.0.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pysbd==0.3.4 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.9.11 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.8.0 +tomli==2.0.2 +tqdm==4.66.5 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==2.2.3 +vcrpy==6.0.2 +wrapt==1.16.0 +xxhash==3.5.0 +yarl==1.15.5 diff --git a/.riot/requirements/146f2d8.txt b/.riot/requirements/146f2d8.txt new file mode 100644 index 00000000000..c65bcdcff42 --- /dev/null +++ b/.riot/requirements/146f2d8.txt @@ -0,0 +1,86 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/146f2d8.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.5.2 +appdirs==1.4.4 +async-timeout==4.0.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.1 +dataclasses-json==0.6.7 +datasets==3.0.1 +dill==0.3.8 +distro==1.9.0 +exceptiongroup==1.2.2 +filelock==3.16.1 +frozenlist==1.4.1 +fsspec[http]==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.26.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.6.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.16 +langchain-community==0.2.17 +langchain-core==0.2.41 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.136 +marshmallow==3.22.0 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.24.4 +openai==1.52.0 +opentracing==2.4.0 +orjson==3.10.7 +packaging==24.1 +pandas==2.0.3 +pluggy==1.5.0 +propcache==0.2.0 +pyarrow==17.0.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pysbd==0.3.4 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.9.11 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.7.0 +tomli==2.0.2 +tqdm==4.66.5 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==1.26.20 +vcrpy==6.0.2 +wrapt==1.16.0 +xxhash==3.5.0 +yarl==1.15.2 diff --git a/.riot/requirements/16562eb.txt b/.riot/requirements/16562eb.txt deleted file mode 100644 index 5eab8360571..00000000000 --- a/.riot/requirements/16562eb.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.7 -# by the following command: -# -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/16562eb.in -# -attrs==23.2.0 -coverage[toml]==7.2.7 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==6.7.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.2.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.11.1 -pyyaml==6.0.1 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.7.1 -urllib3==1.26.19 -vcrpy==4.4.0 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.15.0 diff --git a/.riot/requirements/1687eab.txt b/.riot/requirements/1687eab.txt new file mode 100644 index 00000000000..1ca236f8c07 --- /dev/null +++ b/.riot/requirements/1687eab.txt @@ -0,0 +1,83 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1687eab.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.2.post1 +appdirs==1.4.4 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +dataclasses-json==0.6.7 +datasets==3.0.1 +dill==0.3.8 +distro==1.9.0 +filelock==3.16.1 +frozenlist==1.4.1 +fsspec[http]==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.26.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.6.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.16 +langchain-community==0.2.17 +langchain-core==0.2.41 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.136 +marshmallow==3.23.0 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.26.4 +openai==1.52.0 +opentracing==2.4.0 +orjson==3.10.7 +packaging==24.1 +pandas==2.2.3 +pluggy==1.5.0 +propcache==0.2.0 +pyarrow==17.0.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pysbd==0.3.4 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.9.11 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.8.0 +tqdm==4.66.5 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==2.2.3 +vcrpy==6.0.2 +wrapt==1.16.0 +xxhash==3.5.0 +yarl==1.15.5 diff --git a/.riot/requirements/184f390.txt b/.riot/requirements/184f390.txt deleted file mode 100644 index 7b31e841599..00000000000 --- a/.riot/requirements/184f390.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/184f390.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.19 -vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/1b9d9b7.txt b/.riot/requirements/1b9d9b7.txt deleted file mode 100644 index 88b4a308b15..00000000000 --- a/.riot/requirements/1b9d9b7.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1b9d9b7.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.1 -sortedcontainers==2.4.0 -vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/1d0bd1e.txt b/.riot/requirements/1d0bd1e.txt deleted file mode 100644 index 1346365002f..00000000000 --- a/.riot/requirements/1d0bd1e.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1d0bd1e.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.19 -vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/4102ef5.txt b/.riot/requirements/4102ef5.txt new file mode 100644 index 00000000000..1323e015573 --- /dev/null +++ b/.riot/requirements/4102ef5.txt @@ -0,0 +1,83 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/4102ef5.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.2.post1 +appdirs==1.4.4 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +dataclasses-json==0.6.7 +datasets==3.0.1 +dill==0.3.8 +distro==1.9.0 +filelock==3.16.1 +frozenlist==1.4.1 +fsspec[http]==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.26.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.6.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.16 +langchain-community==0.2.17 +langchain-core==0.2.41 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.136 +marshmallow==3.23.0 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.26.4 +openai==1.52.0 +opentracing==2.4.0 +orjson==3.10.7 +packaging==24.1 +pandas==2.2.3 +pluggy==1.5.0 +propcache==0.2.0 +pyarrow==17.0.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pysbd==0.3.4 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.9.11 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.8.0 +tqdm==4.66.5 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==2.2.3 +vcrpy==6.0.2 +wrapt==1.16.0 +xxhash==3.5.0 +yarl==1.15.5 diff --git a/.riot/requirements/771848b.txt b/.riot/requirements/771848b.txt new file mode 100644 index 00000000000..ba338bba382 --- /dev/null +++ b/.riot/requirements/771848b.txt @@ -0,0 +1,86 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/771848b.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.2.post1 +appdirs==1.4.4 +async-timeout==4.0.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.3 +dataclasses-json==0.6.7 +datasets==3.0.1 +dill==0.3.8 +distro==1.9.0 +exceptiongroup==1.2.2 +filelock==3.16.1 +frozenlist==1.4.1 +fsspec[http]==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +huggingface-hub==0.26.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.6.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.16 +langchain-community==0.2.17 +langchain-core==0.2.41 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.136 +marshmallow==3.23.0 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.26.4 +openai==1.52.0 +opentracing==2.4.0 +orjson==3.10.7 +packaging==24.1 +pandas==2.2.3 +pluggy==1.5.0 +propcache==0.2.0 +pyarrow==17.0.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pysbd==0.3.4 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.9.11 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.8.0 +tomli==2.0.2 +tqdm==4.66.5 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==1.26.20 +vcrpy==6.0.2 +wrapt==1.16.0 +xxhash==3.5.0 +yarl==1.15.5 diff --git a/.riot/requirements/bb8bd85.txt b/.riot/requirements/bb8bd85.txt deleted file mode 100644 index 675b1e946bd..00000000000 --- a/.riot/requirements/bb8bd85.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/bb8bd85.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.1 -sortedcontainers==2.4.0 -vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index e5ba9fc6308..cf915aea35e 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -45,4 +45,12 @@ DROPPED_IO_COLLECTION_ERROR = "dropped_io" DROPPED_VALUE_TEXT = "[This value has been dropped because this span's size exceeds the 1MB size limit.]" +# Set for traces of evaluator integrations e.g. `runner.integration:ragas`. +# Used to differentiate traces of Datadog-run operations vs user-application operations. +RUNNER_IS_INTEGRATION_SPAN_TAG = "runner.integration" + +# The ml app of all ragas traces have this prefix that we use to detect +# whether a span is generated from the ragas evaluation itself. +RAGAS_ML_APP_PREFIX = "dd-ragas" + ANNOTATIONS_CONTEXT_ID = "annotations_context_id" diff --git a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py index d25eddf8bb8..9b70ff51651 100644 --- a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py +++ b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py @@ -1,6 +1,63 @@ +import json import math -import time +import traceback +from typing import List from typing import Optional +from typing import Union + +from ddtrace.internal.logger import get_logger +from ddtrace.internal.telemetry import telemetry_writer +from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL +from ddtrace.internal.utils.version import parse_version +from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX + + +logger = get_logger(__name__) + + +class MiniRagas: + """ + A helper class to store instances of ragas classes and functions + that may or may not exist in a user's environment. + """ + + llm_factory = None + RagasoutputParser = None + faithfulness = None + ensembler = None + get_segmenter = None + StatementFaithfulnessAnswers = None + StatementsAnswers = None + + +def _get_ml_app_for_ragas_trace(span_event: dict) -> str: + """ + The `ml_app` spans generated from traces of ragas will be named as `dd-ragas-` + or `dd-ragas` if `ml_app` is not present in the span event. + """ + tags = span_event.get("tags", []) # list[str] + ml_app = None + for tag in tags: + if isinstance(tag, str) and tag.startswith("ml_app:"): + ml_app = tag.split(":")[1] + break + if not ml_app: + return RAGAS_ML_APP_PREFIX + return "{}-{}".format(RAGAS_ML_APP_PREFIX, ml_app) + + +def _get_faithfulness_instance() -> Optional[object]: + """ + This helper function ensures the faithfulness instance used in + ragas evaluator is updated with the latest ragas faithfulness + instance AND has an non-null llm + """ + if MiniRagas.faithfulness is None: + return None + ragas_faithfulness_instance = MiniRagas.faithfulness + if not ragas_faithfulness_instance.llm: + ragas_faithfulness_instance.llm = MiniRagas.llm_factory() + return ragas_faithfulness_instance class RagasFaithfulnessEvaluator: @@ -13,24 +70,326 @@ class RagasFaithfulnessEvaluator: METRIC_TYPE = "score" def __init__(self, llmobs_service): + """ + Initialize an evaluator that uses the ragas library to generate a faithfulness score on finished LLM spans. + + Faithfulness measures the factual consistency of an LLM's output against a given context. + There are two LLM calls required to generate a faithfulness score - one to generate a set of statements from + the answer, and another to measure the faithfulness of those statements against the context using natural + language entailment. + + For more information, see https://docs.ragas.io/en/latest/concepts/metrics/faithfulness/ + + The `ragas.metrics.faithfulness` instance is used for faithfulness scores. If there is no llm attribute set + on this instance, it will be set to the default `llm_factory()` which uses openai. + + :param llmobs_service: An instance of the LLM Observability service used for tracing the evaluation and + submitting evaluation metrics. + + Raises: NotImplementedError if the ragas library is not found or if ragas version is not supported. + """ self.llmobs_service = llmobs_service + self.ragas_version = "unknown" + telemetry_state = "ok" + try: + import ragas + + self.ragas_version = parse_version(ragas.__version__) + if self.ragas_version >= (0, 2, 0) or self.ragas_version < (0, 1, 10): + raise NotImplementedError( + "Ragas version: {} is not supported for `ragas_faithfulness` evaluator".format(self.ragas_version), + ) + + from ragas.llms import llm_factory + + MiniRagas.llm_factory = llm_factory + + from ragas.llms.output_parser import RagasoutputParser + + MiniRagas.RagasoutputParser = RagasoutputParser + + from ragas.metrics import faithfulness + + MiniRagas.faithfulness = faithfulness + + from ragas.metrics.base import ensembler + + MiniRagas.ensembler = ensembler - def run_and_submit_evaluation(self, span: dict) -> None: - if not span: + from ragas.metrics.base import get_segmenter + + MiniRagas.get_segmenter = get_segmenter + + from ddtrace.llmobs._evaluators.ragas.models import StatementFaithfulnessAnswers + + MiniRagas.StatementFaithfulnessAnswers = StatementFaithfulnessAnswers + + from ddtrace.llmobs._evaluators.ragas.models import StatementsAnswers + + MiniRagas.StatementsAnswers = StatementsAnswers + except Exception as e: + telemetry_state = "fail" + telemetry_writer.add_log( + level=TELEMETRY_LOG_LEVEL.ERROR, + message="Failed to import Ragas dependencies", + stack_trace=traceback.format_exc(), + tags={"ragas_version": self.ragas_version}, + ) + raise NotImplementedError("Failed to load dependencies for `ragas_faithfulness` evaluator") from e + finally: + telemetry_writer.add_count_metric( + namespace="llmobs", + name="evaluators.init", + value=1, + tags=( + ("evaluator_label", self.LABEL), + ("state", telemetry_state), + ("ragas_version", self.ragas_version), + ), + ) + + self.ragas_faithfulness_instance = _get_faithfulness_instance() + self.llm_output_parser_for_generated_statements = MiniRagas.RagasoutputParser( + pydantic_object=MiniRagas.StatementsAnswers + ) + self.llm_output_parser_for_faithfulness_score = MiniRagas.RagasoutputParser( + pydantic_object=MiniRagas.StatementFaithfulnessAnswers + ) + self.split_answer_into_sentences = MiniRagas.get_segmenter( + language=self.ragas_faithfulness_instance.nli_statements_message.language, clean=False + ) + + def run_and_submit_evaluation(self, span_event: dict): + if not span_event: return - score_result = self.evaluate(span) - if score_result: + score_result_or_failure = self.evaluate(span_event) + telemetry_writer.add_count_metric( + "llmobs", + "evaluators.run", + 1, + tags=( + ("evaluator_label", self.LABEL), + ("state", score_result_or_failure if isinstance(score_result_or_failure, str) else "success"), + ), + ) + if isinstance(score_result_or_failure, float): self.llmobs_service.submit_evaluation( - span_context=span, + span_context={"trace_id": span_event.get("trace_id"), "span_id": span_event.get("span_id")}, label=RagasFaithfulnessEvaluator.LABEL, metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, - value=score_result, - timestamp_ms=math.floor(time.time() * 1000), + value=score_result_or_failure, + ) + + def evaluate(self, span_event: dict) -> Union[float, str]: + """ + Performs a faithfulness evaluation on a span event, returning either + - faithfulness score (float) OR + - failure reason (str) + If the ragas faithfulness instance does not have `llm` set, we set `llm` using the `llm_factory()` + method from ragas which defaults to openai's gpt-4o-turbo. + """ + self.ragas_faithfulness_instance = _get_faithfulness_instance() + if not self.ragas_faithfulness_instance: + return "fail_faithfulness_is_none" + + score, question, answer, context, statements, faithfulness_list = math.nan, None, None, None, None, None + + with self.llmobs_service.workflow( + "dd-ragas.faithfulness", ml_app=_get_ml_app_for_ragas_trace(span_event) + ) as ragas_faithfulness_workflow: + try: + faithfulness_inputs = self._extract_faithfulness_inputs(span_event) + if faithfulness_inputs is None: + logger.debug( + "Failed to extract question and context from span sampled for ragas_faithfulness evaluation" + ) + return "fail_extract_faithfulness_inputs" + + question = faithfulness_inputs["question"] + answer = faithfulness_inputs["answer"] + context = faithfulness_inputs["context"] + + statements = self._create_statements(question, answer) + if statements is None: + logger.debug("Failed to create statements from answer for `ragas_faithfulness` evaluator") + return "statements_is_none" + + faithfulness_list = self._create_verdicts(context, statements) + if faithfulness_list is None: + logger.debug("Failed to create faithfulness list `ragas_faithfulness` evaluator") + return "statements_create_faithfulness_list" + + score = self._compute_score(faithfulness_list) + if math.isnan(score): + logger.debug("Score computation returned NaN for `ragas_faithfulness` evaluator") + return "statements_compute_score" + + return score + finally: + self.llmobs_service.annotate( + span=ragas_faithfulness_workflow, + input_data=span_event, + output_data=score, + metadata={ + "statements": statements, + "faithfulness_list": faithfulness_list.dicts() if faithfulness_list is not None else None, + }, + ) + + def _create_statements(self, question: str, answer: str) -> Optional[List[str]]: + with self.llmobs_service.workflow("dd-ragas.create_statements"): + self.llmobs_service.annotate( + input_data={"question": question, "answer": answer}, + ) + statements_prompt = self._create_statements_prompt(answer=answer, question=question) + + """LLM step to break down the answer into simpler statements""" + statements = self.ragas_faithfulness_instance.llm.generate_text(statements_prompt) + + statements = self.llm_output_parser_for_generated_statements.parse(statements.generations[0][0].text) + + if statements is None: + return None + statements = [item["simpler_statements"] for item in statements.dicts()] + statements = [item for sublist in statements for item in sublist] + + self.llmobs_service.annotate( + output_data=statements, + ) + if not isinstance(statements, List): + return None + return statements + + def _create_verdicts(self, context: str, statements: List[str]): + """ + Returns: `StatementFaithfulnessAnswers` model detailing which statements are faithful to the context + """ + with self.llmobs_service.workflow("dd-ragas.create_verdicts") as create_verdicts_workflow: + self.llmobs_service.annotate( + span=create_verdicts_workflow, + input_data=statements, + ) + """Check which statements contradict the conntext""" + raw_nli_results = self.ragas_faithfulness_instance.llm.generate_text( + self._create_natural_language_inference_prompt(context, statements) + ) + if len(raw_nli_results.generations) == 0: + return None + + reproducibility = getattr(self.ragas_faithfulness_instance, "_reproducibility", 1) + + raw_nli_results_texts = [raw_nli_results.generations[0][i].text for i in range(reproducibility)] + + raw_faithfulness_list = [ + faith.dicts() + for faith in [ + self.llm_output_parser_for_faithfulness_score.parse(text) for text in raw_nli_results_texts + ] + if faith is not None + ] + + if len(raw_faithfulness_list) == 0: + return None + + # collapse multiple generations into a single faithfulness list + faithfulness_list = MiniRagas.ensembler.from_discrete(raw_faithfulness_list, "verdict") # type: ignore + try: + return MiniRagas.StatementFaithfulnessAnswers.parse_obj(faithfulness_list) # type: ignore + except Exception as e: + logger.debug("Failed to parse faithfulness_list", exc_info=e) + return None + finally: + self.llmobs_service.annotate( + span=create_verdicts_workflow, + output_data=faithfulness_list, + ) + + def _extract_faithfulness_inputs(self, span_event: dict) -> Optional[dict]: + """ + Extracts the question, answer, and context used as inputs to faithfulness + evaluation from a span event. + + question - input.prompt.variables.question OR input.messages[-1].content + context - input.prompt.variables.context + answer - output.messages[-1].content + """ + with self.llmobs_service.workflow("dd-ragas.extract_faithfulness_inputs") as extract_inputs_workflow: + self.llmobs_service.annotate(span=extract_inputs_workflow, input_data=span_event) + question, answer, context = None, None, None + + meta_io = span_event.get("meta") + if meta_io is None: + return None + + meta_input = meta_io.get("input") + meta_output = meta_io.get("output") + + if not (meta_input and meta_output): + return None + + prompt = meta_input.get("prompt") + if prompt is None: + logger.debug("Failed to extract `prompt` from span for `ragas_faithfulness` evaluation") + return None + prompt_variables = prompt.get("variables") + + input_messages = meta_input.get("messages") + + messages = meta_output.get("messages") + if messages is not None and len(messages) > 0: + answer = messages[-1].get("content") + + if prompt_variables: + question = prompt_variables.get("question") + context = prompt_variables.get("context") + + if not question and len(input_messages) > 0: + question = input_messages[-1].get("content") + + self.llmobs_service.annotate( + span=extract_inputs_workflow, output_data={"question": question, "context": context, "answer": answer} + ) + if any(field is None for field in (question, context, answer)): + logger.debug("Failed to extract inputs required for faithfulness evaluation") + return None + + return {"question": question, "context": context, "answer": answer} + + def _create_statements_prompt(self, answer, question): + # Returns: `ragas.llms.PromptValue` object + with self.llmobs_service.task("dd-ragas.create_statements_prompt"): + sentences = self.split_answer_into_sentences.segment(answer) + sentences = [sentence for sentence in sentences if sentence.strip().endswith(".")] + sentences = "\n".join([f"{i}:{x}" for i, x in enumerate(sentences)]) + return self.ragas_faithfulness_instance.statement_prompt.format( + question=question, answer=answer, sentences=sentences ) - def evaluate(self, span: dict) -> Optional[float]: - """placeholder function""" - span_id, trace_id = span.get("span_id"), span.get("trace_id") - if not span_id or not trace_id: - return None - return 1.0 + def _create_natural_language_inference_prompt(self, context_str: str, statements: List[str]): + # Returns: `ragas.llms.PromptValue` object + with self.llmobs_service.task("dd-ragas.create_natural_language_inference_prompt"): + prompt_value = self.ragas_faithfulness_instance.nli_statements_message.format( + context=context_str, statements=json.dumps(statements) + ) + return prompt_value + + def _compute_score(self, faithfulness_list) -> float: + """ + Args: + faithfulness_list (StatementFaithfulnessAnswers): a list of statements and their faithfulness verdicts + """ + with self.llmobs_service.task("dd-ragas.compute_score"): + faithful_statements = sum(1 if answer.verdict else 0 for answer in faithfulness_list.__root__) + num_statements = len(faithfulness_list.__root__) + if num_statements: + score = faithful_statements / num_statements + else: + score = math.nan + self.llmobs_service.annotate( + metadata={ + "faithful_statements": faithful_statements, + "num_statements": num_statements, + }, + output_data=score, + ) + return score diff --git a/ddtrace/llmobs/_evaluators/ragas/models.py b/ddtrace/llmobs/_evaluators/ragas/models.py new file mode 100644 index 00000000000..5ee4d433c33 --- /dev/null +++ b/ddtrace/llmobs/_evaluators/ragas/models.py @@ -0,0 +1,36 @@ +from typing import Dict +from typing import List + +from langchain_core.pydantic_v1 import BaseModel +from langchain_core.pydantic_v1 import Field + + +""" +This module contains helper pydantic models to validate the inputs +and outputs of LLM calls used for Ragas +""" + + +class StatementFaithfulnessAnswer(BaseModel): + statement: str = Field(..., description="the original statement, word-by-word") + reason: str = Field(..., description="the reason of the verdict") + verdict: int = Field(..., description="the verdict(0/1) of the faithfulness.") + + +class StatementFaithfulnessAnswers(BaseModel): + __root__: List[StatementFaithfulnessAnswer] + + def dicts(self) -> List[Dict]: + return self.dict()["__root__"] + + +class Statements(BaseModel): + sentence_index: int = Field(..., description="Index of the sentence from the statement list") + simpler_statements: List[str] = Field(..., description="the simpler statements") + + +class StatementsAnswers(BaseModel): + __root__: List[Statements] + + def dicts(self) -> List[Dict]: + return self.dict()["__root__"] diff --git a/ddtrace/llmobs/_evaluators/runner.py b/ddtrace/llmobs/_evaluators/runner.py index 7f08b258f62..8e535403c94 100644 --- a/ddtrace/llmobs/_evaluators/runner.py +++ b/ddtrace/llmobs/_evaluators/runner.py @@ -1,21 +1,21 @@ -import atexit from concurrent import futures import os from typing import Dict from ddtrace import Span from ddtrace.internal import forksafe -from ddtrace.internal import service from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import PeriodicService +from ddtrace.internal.telemetry import telemetry_writer from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator from ddtrace.llmobs._evaluators.sampler import EvaluatorRunnerSampler logger = get_logger(__name__) + SUPPORTED_EVALUATORS = { - "ragas_faithfulness": RagasFaithfulnessEvaluator, + RagasFaithfulnessEvaluator.LABEL: RagasFaithfulnessEvaluator, } @@ -47,7 +47,22 @@ def __init__(self, interval: float, llmobs_service=None, evaluators=None): evaluators = evaluator_str.split(",") for evaluator in evaluators: if evaluator in SUPPORTED_EVALUATORS: - self.evaluators.append(SUPPORTED_EVALUATORS[evaluator](llmobs_service=llmobs_service)) + evaluator_init_state = "ok" + try: + self.evaluators.append(SUPPORTED_EVALUATORS[evaluator](llmobs_service=llmobs_service)) + except NotImplementedError as e: + evaluator_init_state = "error" + raise e + finally: + telemetry_writer.add_count_metric( + namespace="llmobs", + name="evaluators.init", + value=1, + tags=( + ("evaluator_label", evaluator), + ("state", evaluator_init_state), + ), + ) def start(self, *args, **kwargs): if not self.evaluators: @@ -55,12 +70,14 @@ def start(self, *args, **kwargs): return super(EvaluatorRunner, self).start() logger.debug("started %r to %r", self.__class__.__name__) - atexit.register(self.on_shutdown) - def stop(self, *args, **kwargs): - if self.status == service.ServiceStatus.STOPPED: - return - super(EvaluatorRunner, self).stop() + def _stop_service(self) -> None: + """ + Ensures all spans are evaluated & evaluation metrics are submitted when evaluator runner + is stopped by the LLM Obs instance + """ + self.periodic(_wait_sync=True) + self.executor.shutdown(wait=True) def recreate(self) -> "EvaluatorRunner": return self.__class__( @@ -69,9 +86,6 @@ def recreate(self) -> "EvaluatorRunner": evaluators=self.evaluators, ) - def on_shutdown(self): - self.executor.shutdown() - def enqueue(self, span_event: Dict, span: Span) -> None: with self._lock: if len(self._buffer) >= self._buffer_limit: @@ -81,7 +95,12 @@ def enqueue(self, span_event: Dict, span: Span) -> None: return self._buffer.append((span_event, span)) - def periodic(self) -> None: + def periodic(self, _wait_sync=False) -> None: + """ + :param bool _wait_sync: if `True`, each evaluator is run for each span in the buffer + synchronously. This param is only set to `True` for when the evaluator runner is stopped by the LLM Obs + instance on process exit and we want to block until all spans are evaluated and metrics are submitted. + """ with self._lock: if not self._buffer: return @@ -89,17 +108,20 @@ def periodic(self) -> None: self._buffer = [] try: - self.run(span_events_and_spans) + if not _wait_sync: + for evaluator in self.evaluators: + self.executor.map( + lambda span_event: evaluator.run_and_submit_evaluation(span_event), + [ + span_event + for span_event, span in span_events_and_spans + if self.sampler.sample(evaluator.LABEL, span) + ], + ) + else: + for evaluator in self.evaluators: + for span_event, span in span_events_and_spans: + if self.sampler.sample(evaluator.LABEL, span): + evaluator.run_and_submit_evaluation(span_event) except RuntimeError as e: logger.debug("failed to run evaluation: %s", e) - - def run(self, span_events_and_spans): - for evaluator in self.evaluators: - self.executor.map( - lambda span: evaluator.run_and_submit_evaluation(span), - [ - span_event - for span_event, span in span_events_and_spans - if self.sampler.sample(evaluator.LABEL, span) - ], - ) diff --git a/ddtrace/llmobs/_evaluators/sampler.py b/ddtrace/llmobs/_evaluators/sampler.py index a3298603656..0fbcad53536 100644 --- a/ddtrace/llmobs/_evaluators/sampler.py +++ b/ddtrace/llmobs/_evaluators/sampler.py @@ -7,6 +7,8 @@ from ddtrace import config from ddtrace.internal.logger import get_logger +from ddtrace.internal.telemetry import telemetry_writer +from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.sampling_rule import SamplingRule @@ -57,8 +59,18 @@ def sample(self, evaluator_label, span): def parse_rules(self) -> List[EvaluatorRunnerSamplingRule]: rules = [] sampling_rules_str = os.getenv(self.SAMPLING_RULES_ENV_VAR) + telemetry_writer.add_configuration("_DD_LLMOBS_EVALUATOR_SAMPLING_RULES", sampling_rules_str, origin="env") def parsing_failed_because(msg, maybe_throw_this): + telemetry_writer.add_log( + TELEMETRY_LOG_LEVEL.ERROR, message="Evaluator sampling parsing failure because: {}".format(msg) + ) + telemetry_writer.add_count_metric( + namespace="llmobs", + name="evaluators.error", + value=1, + tags=(("reason", "sampling_rule_parsing_failure"),), + ) if config._raise: raise maybe_throw_this(msg) logger.warning(msg, exc_info=True) @@ -90,5 +102,11 @@ def parsing_failed_because(msg, maybe_throw_this): continue span_name = rule.get(EvaluatorRunnerSamplingRule.SPAN_NAME_KEY, SamplingRule.NO_RULE) evaluator_label = rule.get(EvaluatorRunnerSamplingRule.EVALUATOR_LABEL_KEY, SamplingRule.NO_RULE) + telemetry_writer.add_distribution_metric( + "llmobs", + "evaluators.rule_sample_rate", + sample_rate, + tags=(("evaluator_label", evaluator_label), ("span_name", span_name)), + ) rules.append(EvaluatorRunnerSamplingRule(sample_rate, evaluator_label, span_name)) return rules diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 1e8caa99861..15fbc23050c 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -145,15 +145,18 @@ def _start_service(self) -> None: def _stop_service(self) -> None: try: - self._llmobs_span_writer.stop() - self._llmobs_eval_metric_writer.stop() + self._evaluator_runner.stop() + # flush remaining evaluation spans & evaluations + self._instance._llmobs_span_writer.periodic() + self._instance._llmobs_eval_metric_writer.periodic() except ServiceStatusError: - log.debug("Error stopping LLMObs writers") + log.debug("Error stopping evaluator runner") try: - self._evaluator_runner.stop() + self._llmobs_span_writer.stop() + self._llmobs_eval_metric_writer.stop() except ServiceStatusError: - log.debug("Error stopping evaluator runner") + log.debug("Error stopping LLMObs writers") try: forksafe.unregister(self._child_after_fork) @@ -258,8 +261,8 @@ def disable(cls) -> None: log.debug("Disabling %s", cls.__name__) atexit.unregister(cls.disable) - cls.enabled = False cls._instance.stop() + cls.enabled = False cls._instance.tracer.deregister_on_start_span(cls._instance._do_annotations) telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.LLMOBS, False) diff --git a/ddtrace/llmobs/_trace_processor.py b/ddtrace/llmobs/_trace_processor.py index beca9684c6f..d6ac9b428ab 100644 --- a/ddtrace/llmobs/_trace_processor.py +++ b/ddtrace/llmobs/_trace_processor.py @@ -3,6 +3,7 @@ from typing import Dict from typing import List from typing import Optional +from typing import Tuple import ddtrace from ddtrace import Span @@ -27,6 +28,8 @@ from ddtrace.llmobs._constants import OUTPUT_MESSAGES from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import PARENT_ID_KEY +from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX +from ddtrace.llmobs._constants import RUNNER_IS_INTEGRATION_SPAN_TAG from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._constants import TAGS @@ -59,18 +62,20 @@ def process_trace(self, trace: List[Span]) -> Optional[List[Span]]: def submit_llmobs_span(self, span: Span) -> None: """Generate and submit an LLMObs span event to be sent to LLMObs.""" span_event = None + is_llm_span = span.get_tag(SPAN_KIND) == "llm" + is_ragas_integration_span = False try: - span_event = self._llmobs_span_event(span) + span_event, is_ragas_integration_span = self._llmobs_span_event(span) self._span_writer.enqueue(span_event) except (KeyError, TypeError): log.error("Error generating LLMObs span event for span %s, likely due to malformed span", span) finally: - if not span_event: + if not span_event or not is_llm_span or is_ragas_integration_span: return if self._evaluator_runner: self._evaluator_runner.enqueue(span_event, span) - def _llmobs_span_event(self, span: Span) -> Dict[str, Any]: + def _llmobs_span_event(self, span: Span) -> Tuple[Dict[str, Any], bool]: """Span event object structure.""" span_kind = span._meta.pop(SPAN_KIND) meta: Dict[str, Any] = {"span.kind": span_kind, "input": {}, "output": {}} @@ -111,6 +116,12 @@ def _llmobs_span_event(self, span: Span) -> Dict[str, Any]: meta.pop("output") metrics = json.loads(span._meta.pop(METRICS, "{}")) ml_app = _get_ml_app(span) + + is_ragas_integration_span = False + + if ml_app.startswith(RAGAS_ML_APP_PREFIX): + is_ragas_integration_span = True + span.set_tag_str(ML_APP, ml_app) parent_id = str(_get_llmobs_parent_id(span) or "undefined") @@ -132,12 +143,16 @@ def _llmobs_span_event(self, span: Span) -> Dict[str, Any]: span.set_tag_str(SESSION_ID, session_id) llmobs_span_event["session_id"] = session_id - llmobs_span_event["tags"] = self._llmobs_tags(span, ml_app, session_id) + llmobs_span_event["tags"] = self._llmobs_tags( + span, ml_app, session_id, is_ragas_integration_span=is_ragas_integration_span + ) - return llmobs_span_event + return llmobs_span_event, is_ragas_integration_span @staticmethod - def _llmobs_tags(span: Span, ml_app: str, session_id: Optional[str] = None) -> List[str]: + def _llmobs_tags( + span: Span, ml_app: str, session_id: Optional[str] = None, is_ragas_integration_span: bool = False + ) -> List[str]: tags = { "version": config.version or "", "env": config.env or "", @@ -153,6 +168,8 @@ def _llmobs_tags(span: Span, ml_app: str, session_id: Optional[str] = None) -> L tags["error_type"] = err_type if session_id: tags["session_id"] = session_id + if is_ragas_integration_span: + tags[RUNNER_IS_INTEGRATION_SPAN_TAG] = "ragas" existing_tags = span._meta.pop(TAGS, None) if existing_tags is not None: tags.update(json.loads(existing_tags)) diff --git a/riotfile.py b/riotfile.py index df91b4ae652..61d0e0b87db 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2819,6 +2819,9 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): command="pytest {cmdargs} tests/llmobs", pkgs={"vcrpy": latest, "pytest-asyncio": "==0.21.1"}, pys=select_pys(min_version="3.7"), + venvs=[ + Venv(pys=select_pys(min_version="3.8"), pkgs={"ragas": "==0.1.21", "langchain": latest}), + ], ), Venv( name="profile", diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index d13e95b7e6e..afafdaf4aab 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -19,7 +19,7 @@ cassette_library_dir=os.path.join(os.path.dirname(__file__), "llmobs_cassettes/"), record_mode="once", match_on=["path"], - filter_headers=[("DD-API-KEY", "XXXXXX")], + filter_headers=["authorization", "OpenAI-Organization", "api-key", "x-api-key", ("DD-API-KEY", "XXXXXX")], # Ignore requests to the agent ignore_localhost=True, ) @@ -56,6 +56,7 @@ def _expected_llmobs_tags(span, error=None, tags=None, session_id=None): def _expected_llmobs_llm_span_event( span, span_kind="llm", + prompt=None, input_messages=None, input_documents=None, output_messages=None, @@ -94,6 +95,8 @@ def _expected_llmobs_llm_span_event( meta_dict["input"].update({"messages": input_messages}) if output_messages is not None: meta_dict["output"].update({"messages": output_messages}) + if prompt is not None: + meta_dict["input"].update({"prompt": prompt}) if span_kind == "embedding": if input_documents is not None: meta_dict["input"].update({"documents": input_documents}) @@ -442,6 +445,54 @@ def _oversized_retrieval_event(): } +def expected_ragas_trace_tags(): + return [ + "version:", + "env:", + "service:", + "source:integration", + "ml_app:dd-ragas-unnamed-ml-app", + "ddtrace.version:{}".format(ddtrace.__version__), + "language:python", + "error:0", + "runner.integration:ragas", + ] + + +default_ragas_inputs = { + "question": "What is the capital of France?", + "context": "The capital of France is Paris.", + "answer": "The capital of France is Paris", +} + + +def _llm_span_with_expected_ragas_inputs_in_prompt(ragas_inputs=None): + if not ragas_inputs: + ragas_inputs = default_ragas_inputs + + return _expected_llmobs_llm_span_event( + span=Span("dummy"), + prompt={ + "variables": {"question": ragas_inputs["question"], "context": ragas_inputs["context"]}, + }, + output_messages=[{"content": ragas_inputs["answer"]}], + ) + + +def _llm_span_with_expected_ragas_inputs_in_messages(ragas_inputs=None): + if not ragas_inputs: + ragas_inputs = default_ragas_inputs + + return _expected_llmobs_llm_span_event( + span=Span("dummy"), + prompt={ + "variables": {"context": ragas_inputs["context"]}, + }, + input_messages=[{"content": ragas_inputs["question"]}], + output_messages=[{"content": ragas_inputs["answer"]}], + ) + + class DummyEvaluator: LABEL = "dummy" @@ -455,3 +506,118 @@ def run_and_submit_evaluation(self, span): value=1.0, metric_type="score", ) + + +def _expected_ragas_spans(ragas_inputs=None): + if not ragas_inputs: + ragas_inputs = default_ragas_inputs + return [ + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": "undefined", + "name": "dd-ragas.faithfulness", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": { + "span.kind": "workflow", + "input": {"value": mock.ANY}, + "output": {"value": "1.0"}, + "metadata": { + "statements": mock.ANY, + "faithfulness_list": mock.ANY, + }, + }, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.extract_faithfulness_inputs", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": { + "span.kind": "workflow", + "input": {"value": mock.ANY}, + "output": {"value": mock.ANY}, + }, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.create_statements", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": { + "span.kind": "workflow", + "input": {"value": mock.ANY}, + "output": {"value": mock.ANY}, + }, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.create_statements_prompt", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": {"span.kind": "task"}, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.create_verdicts", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": { + "span.kind": "workflow", + "input": {"value": mock.ANY}, + "output": {"value": mock.ANY}, + }, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.create_natural_language_inference_prompt", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": {"span.kind": "task"}, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + { + "trace_id": mock.ANY, + "span_id": mock.ANY, + "parent_id": mock.ANY, + "name": "dd-ragas.compute_score", + "start_ns": mock.ANY, + "duration": mock.ANY, + "status": "ok", + "meta": { + "span.kind": "task", + "output": {"value": "1.0"}, + "metadata": {"faithful_statements": 1, "num_statements": 1}, + }, + "metrics": {}, + "tags": expected_ragas_trace_tags(), + }, + ] diff --git a/tests/llmobs/conftest.py b/tests/llmobs/conftest.py index fbe38232cd5..90f7b8a8b97 100644 --- a/tests/llmobs/conftest.py +++ b/tests/llmobs/conftest.py @@ -8,6 +8,7 @@ from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator from tests.llmobs._utils import logs_vcr from tests.utils import DummyTracer +from tests.utils import override_env from tests.utils import override_global_config from tests.utils import request_token @@ -59,6 +60,16 @@ def mock_llmobs_eval_metric_writer(): patcher.stop() +@pytest.fixture +def mock_llmobs_submit_evaluation(): + patcher = mock.patch("ddtrace.llmobs._llmobs.LLMObs.submit_evaluation") + LLMObsMock = patcher.start() + m = mock.MagicMock() + LLMObsMock.return_value = m + yield m + patcher.stop() + + @pytest.fixture def mock_http_writer_send_payload_response(): with mock.patch( @@ -141,7 +152,48 @@ def AgentlessLLMObs(mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_w @pytest.fixture -def mock_ragas_evaluator(mock_llmobs_eval_metric_writer): - with mock.patch("ddtrace.llmobs._evaluators.ragas.faithfulness.RagasFaithfulnessEvaluator.evaluate") as m: - m.return_value = 1.0 - yield RagasFaithfulnessEvaluator +def mock_llmobs_evaluator_runner(): + patcher = mock.patch("ddtrace.llmobs._evaluators.runner.EvaluatorRunner.enqueue") + LLMObsMockEvaluatorRunner = patcher.start() + m = mock.MagicMock() + LLMObsMockEvaluatorRunner.return_value = m + yield m + patcher.stop() + + +@pytest.fixture +def mock_ragas_dependencies_not_present(): + import ragas + + previous = ragas.__version__ + ## unsupported version + ragas.__version__ = "0.0.0" + yield + ragas.__version__ = previous + + +@pytest.fixture +def ragas(mock_llmobs_span_writer, mock_llmobs_eval_metric_writer): + with override_global_config(dict(_dd_api_key="")): + import ragas + + with override_env(dict(OPENAI_API_KEY=os.getenv("OPENAI_API_KEY", ""))): + yield ragas + + +@pytest.fixture +def reset_ragas_faithfulness_llm(): + import ragas + + previous_llm = ragas.metrics.faithfulness.llm + yield + ragas.metrics.faithfulness.llm = previous_llm + + +@pytest.fixture +def mock_ragas_evaluator(mock_llmobs_eval_metric_writer, ragas): + patcher = mock.patch("ddtrace.llmobs._evaluators.ragas.faithfulness.RagasFaithfulnessEvaluator.evaluate") + LLMObsMockRagas = patcher.start() + LLMObsMockRagas.return_value = 1.0 + yield RagasFaithfulnessEvaluator + patcher.stop() diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.send_score_metric.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.send_score_metric.yaml index d715994c439..e2e17e715cf 100644 --- a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.send_score_metric.yaml +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.send_score_metric.yaml @@ -2,7 +2,7 @@ interactions: - request: body: '{"data": {"type": "evaluation_metric", "attributes": {"metrics": [{"span_id": "123", "trace_id": "1234", "label": "dummy", "metric_type": "score", "timestamp_ms": - 1728480443772, "score_value": 1.0, "ml_app": "unnamed-ml-app", "tags": ["ddtrace.version:2.14.0.dev196+g7cf7989ab", + 1729569649880, "score_value": 1.0, "ml_app": "unnamed-ml-app", "tags": ["ddtrace.version:2.15.0.dev219+ge047e25bb.d20241022", "ml_app:unnamed-ml-app"]}]}}}' headers: Content-Type: @@ -13,16 +13,16 @@ interactions: uri: https://api.datad0g.com/api/intake/llm-obs/v1/eval-metric response: body: - string: '{"data":{"id":"ccf36d1a-6153-4042-ba2d-a5ec5896a6ac","type":"evaluation_metric","attributes":{"metrics":[{"id":"bYp4oTawxz","trace_id":"1234","span_id":"123","timestamp_ms":1728480443772,"ml_app":"unnamed-ml-app","metric_type":"score","label":"dummy","score_value":1,"tags":["ddtrace.version:2.14.0.dev196+g7cf7989ab","ml_app:unnamed-ml-app"]}]}}}' + string: '{"data":{"id":"2131dbc0-d085-401c-8b2d-8506a9ac8c13","type":"evaluation_metric","attributes":{"metrics":[{"id":"YutAyQc6F4","trace_id":"1234","span_id":"123","timestamp_ms":1729569649880,"ml_app":"unnamed-ml-app","metric_type":"score","label":"dummy","score_value":1,"tags":["ddtrace.version:2.15.0.dev219+ge047e25bb.d20241022","ml_app:unnamed-ml-app"]}]}}}' headers: content-length: - - '347' + - '357' content-security-policy: - frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pub293163a918901030b79492fe1ab424cf&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatad0g.com content-type: - application/vnd.api+json date: - - Wed, 09 Oct 2024 13:27:25 GMT + - Tue, 22 Oct 2024 04:00:50 GMT strict-transport-security: - max-age=31536000; includeSubDomains; preload vary: diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_periodic_enqueues_eval_metric.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_periodic_enqueues_eval_metric.yaml new file mode 100644 index 00000000000..ed694a4391c --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_periodic_enqueues_eval_metric.yaml @@ -0,0 +1,271 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dJFPj9owEMXv+RSjOZNVklKycKPaReqhUtU/UluCEmMmiVvHtjxGAiG+ + e+XAAj304sP7+T2/GZ8SAFQ7XADKXgQ5OJ0uP7yT5vBRfX05vP46flkuV99/9tN21X86/iCcRIfd + /iYZ3lxP0g5OU1DWXLD0JALF1Lwsynz+PM/ej2CwO9LR1rmQTm06KKPSIiumaVam+fPV3VsliXEB + 6wQA4DSesafZ0QEXkE3elIGYRUe4uF0CQG91VFAwKw7CBJzcobQmkBmrN02zPlXIFBVJ9RhfjflQ + Ias4k685iEADmcARrSv81hNI4VQQGmwLKy+MJFAMn4VX/FTh5rxpmubxUU/tnkUc3Oy1vurn2xTa + ds7bLV/5TW+VUdzXngRbExtzsA5Hek4ANuO29v8sAJ23gwt1sH/IxMBZll/y8P5Jd1rMrjDYIPSD + qyj/56p3FITS/LBzvDRUprsnZLea45zIRw401K0yHXnn1eULWlfn2+10ls/Kdo7JOfkLAAD//wMA + 1fWuKJACAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c847da6df5d8c7b-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 17:28:26 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=I1uA6Uw_kSHTz4C5HJeEgN8tlgahA.w4lNRH3SUXlTw-1727198906-1.0.1.1-jjjdJBAcMMJ3HfLtMRYpZMtzklqRQSbb7p235vOXdV1gO9TwAerAZ.q8lENyNlhVsGbZl0PRhc01Od06TKaPFg; + path=/; expires=Tue, 24-Sep-24 17:58:26 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=cIQ0gHwgf7xZrO.PtD6BOsLZOjkubjxa5vGZKkAyIgQ-1727198906317-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + openai-organization: + - datadog-staging + openai-processing-ms: + - '658' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_5a4a2d6012766c41e1df75ca1f91f8c0 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=I1uA6Uw_kSHTz4C5HJeEgN8tlgahA.w4lNRH3SUXlTw-1727198906-1.0.1.1-jjjdJBAcMMJ3HfLtMRYpZMtzklqRQSbb7p235vOXdV1gO9TwAerAZ.q8lENyNlhVsGbZl0PRhc01Od06TKaPFg; + _cfuvid=cIQ0gHwgf7xZrO.PtD6BOsLZOjkubjxa5vGZKkAyIgQ-1727198906317-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dFFBbtswELz7FQuerUByDMvWrUERBCgKtEXbQ6NAoqiVtKlEsuQ6cGD4 + 7wVlRUoPvfAwszOcnT2vAATVIgOhOslqsH304e5WmdNr9ePh1yf+/C3dfDx+/XnH3X3y51SJdVCY + 6hkVv6lulBlsj0xGX2nlUDIG1yTdpMlhf4h3IzGYGvsgay1HWxMNpCnaxJttFKdRsp/UnSGFXmTw + uAIAOI9vyKlrPIkM4vUbMqD3skWRzUMAwpk+IEJ6T56lZrFeSGU0ox6jl2X5eM6FZ8k4oOZcZJCL + 7x2CkpZY9mAauHdSKwTy8EU68je5WEMuHEpv9CKYPcKghJocKgaHFplCLcGJOwTSjXGDHCHrzAvV + WAPpkRuTnXj64QVdTWrMlFyeyrJ8v4TD5uhlKFIf+37CL3MrvWmtM5Wf+BlvSJPvimv40IBnY8XI + XlYAT2P7x38KFdaZwXLB5jfqYJim+6ufWI6+sLeHiWTDsl/wfZL+T1XUyJJ6/+6GU72k28UhnmOO + ewr/6hmHoiHdorOOridtbJFU1XaX7NLmIFaX1V8AAAD//wMAuSoB7eACAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c847dacae048c7b-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 17:28:27 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + openai-organization: + - datadog-staging + openai-processing-ms: + - '692' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_376662532427f06b013847368172d0b6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_timed_enqueues_eval_metric.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_timed_enqueues_eval_metric.yaml new file mode 100644 index 00000000000..4799bb9676b --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_evaluator_runner.test_evaluator_runner_timed_enqueues_eval_metric.yaml @@ -0,0 +1,270 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + cookie: + - __cf_bm=I1uA6Uw_kSHTz4C5HJeEgN8tlgahA.w4lNRH3SUXlTw-1727198906-1.0.1.1-jjjdJBAcMMJ3HfLtMRYpZMtzklqRQSbb7p235vOXdV1gO9TwAerAZ.q8lENyNlhVsGbZl0PRhc01Od06TKaPFg; + _cfuvid=cIQ0gHwgf7xZrO.PtD6BOsLZOjkubjxa5vGZKkAyIgQ-1727198906317-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dJFPj9owEMXvfIrRnMkqSVGycGtVVVWlSj1UW6kEJcZMglvHdj1D/yG+ + e+XAAnvoxYf383t+Mz7OANDscAWo90r0GGz2+s0rHX79/Nv7jz+eQj68r99++PqkvlTG1zXOk8Nv + v5GWZ9eD9mOwJMa7M9aRlFBKLeqyLpaPy7yewOh3ZJNtCJItfDYaZ7IyLxdZXmfF48W990YT4wrW + MwCA43Smnm5Hv3EF+fxZGYlZDYSr6yUAjN4mBRWzYVFOcH6D2jshN1Xvum59bJApKZraKb6Z8qFB + Nmmm2LIooZGccELrBj/vCbQKRpQF38O7qJwmMAyfVDT80ODmtOm67v7RSP2BVRrcHay96KfrFNYP + IfotX/hV740zvG8jKfYuNWbxASd6mgFspm0dXiwAQ/RjkFb8d3IpsMqLcx7ePulGy+oCxYuyd66y + /p+r3ZEoY/lu53huaNxwS8ivNac5kf+w0Nj2xg0UQzTnL+hDW2y3i6qo6n6Js9PsHwAAAP//AwAm + auP/kAIAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c847db2ac748c7b-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 17:28:27 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + openai-organization: + - datadog-staging + openai-processing-ms: + - '373' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_baa95df2630ee4e5833bada05f9e7107 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=I1uA6Uw_kSHTz4C5HJeEgN8tlgahA.w4lNRH3SUXlTw-1727198906-1.0.1.1-jjjdJBAcMMJ3HfLtMRYpZMtzklqRQSbb7p235vOXdV1gO9TwAerAZ.q8lENyNlhVsGbZl0PRhc01Od06TKaPFg; + _cfuvid=cIQ0gHwgf7xZrO.PtD6BOsLZOjkubjxa5vGZKkAyIgQ-1727198906317-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dFHBbpwwEL3zFSOflwg224Xl1kTqsa2i9tIQgdcMMC3Yjj0bpVrtv1dm + CSSHXnx4b97zmzfnCEBQIwoQqpesRjvEn+9u1fOD+irx4T7Nxv2O+p76Z/Pz269PWmyCwhx/o+I3 + 1Y0yox2Qycy0cigZg2uabbP0kB+SfCJG0+AQZJ3leGfikTTF22S7i5MsTvNZ3RtS6EUBjxEAwHl6 + Q07d4KsoINm8ISN6LzsUxTIEIJwZAiKk9+RZahablVRGM+opel3Xj+dSeJaMI2ouRQGl+NEjKGmJ + 5QCmhS9OaoVAHr5LR/6mFBsohUPpjV4Fi0cYlNCQQ8Xg0CJTqCU4cY9AujVulBNknXmhBhsgPXFT + sleef3hB15CaMqWXp7qu3y/hsD15GYrUp2GY8cvSymA668zRz/yCt6TJ99U1fGjAs7FiYi8RwNPU + /ulDocI6M1qu2PxBHQyzLL/6ifXoK3t7mEk2LIcVz9Psf6qqQZY0+Hc3nOsl3a0OyRJz2lP4v55x + rFrSHTrr6HrS1lbp8bjbp/usPYjoEv0DAAD//wMAr2pwbuACAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c847db6c8bd8c7b-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 17:28:28 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '723' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_abc83078f2db82b2b87f90efc169ee29 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.emits_traces_and_evaluations_on_exit.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.emits_traces_and_evaluations_on_exit.yaml new file mode 100644 index 00000000000..757f875443f --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.emits_traces_and_evaluations_on_exit.yaml @@ -0,0 +1,315 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2yRW4vbMBCF3/0rxDzHi+2mySZvbUOhsPRGoSxxsBV5bKuVJaGZ9ELIfy9yvEnK + 7oseztH5NGd0TIQA3cBagOolq8Gb9M3D5tPi1zuz+r0qHjavv27a74+vPj5+sG8z8wVmMeH2P1Dx + U+pOucEbZO3s2VYBJWOk5stitchXy3w+GoNr0MRY5zmdu3TQVqdFVszTbJnm91O6d1ohwVpsEyGE + OI5nnNM2+AfWIps9KQMSyQ5hfbkkBARnogKSSBNLyzC7mspZRjuOXtf19lgCYVQUViO+HPmiBNKx + U6iIJeOAlila2xK+9SiU9JqlEa4V74O0CoUm8VkGTXcl7E67uq5vHw3YHkjG4vZgzKSfLi2M63xw + e5r8i95qq6mvAkpyNk5M7DyM7ikRYjdu6/DfAsAHN3iu2P1EG4GLLD/z4PpJV7dYTCY7luYmVSxn + L/CqBllqQzf7BiVVj801miU35Z4/+hLiXFDb7hklmUhAf4lxqFptOww+6PMPtr6a3xeqKORyryA5 + Jf8AAAD//wMAn6C7Cc8CAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d6b5b701f294367-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 22 Oct 2024 17:55:15 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=iQaF937ylY7BvvBCyWYQoxiJwi1nBp5.LILrHLw1uno-1729619715-1.0.1.1-jS4Dz7yc_ud.hKZlJ_CAZkSQesqzVkfrA5F30zI7CtJsbEKyAiuVlpX0CPf816UtlhXQEW8T5nsc.UvnsCOzOw; + path=/; expires=Tue, 22-Oct-24 18:25:15 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=wQzHCwLW6CPU768K_tlLklWp36I8zYCVJkKlAMtnMkk-1729619715162-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '496' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_33b8cddecaab8b8bc36e90f58f844636 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=iQaF937ylY7BvvBCyWYQoxiJwi1nBp5.LILrHLw1uno-1729619715-1.0.1.1-jS4Dz7yc_ud.hKZlJ_CAZkSQesqzVkfrA5F30zI7CtJsbEKyAiuVlpX0CPf816UtlhXQEW8T5nsc.UvnsCOzOw; + _cfuvid=wQzHCwLW6CPU768K_tlLklWp36I8zYCVJkKlAMtnMkk-1729619715162-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2xSQW7bMBC86xWLPVuBpTqR7VuAoGiBAmmLHhrEgUVTK2tdiSTIdZDA8N8Lyorl + ILnwMLMznB3ykAAgV7gE1I0S3bk2vf1xd19M/c3u4frhu/37Tet73qm7383r7fwXTqLCbnak5U11 + pW3nWhK25kRrT0ooumZFvrjJFkV23ROdraiNsq2TdGbTjg2n+TSfpdMizeaDurGsKeASHhMAgEN/ + xpymohdcwnTyhnQUgtoSLs9DAOhtGxFUIXAQZQQnI6mtETJ99LIsHw8rDKKEOjKywiWs8E9DoJVj + US3YGr56ZTQBB/ipPIerFU5ghZ5UsGYUnD3ioIKKPWkBT46EYy3RSRoCNrX1neoh5+0zV1QBm57r + k73IcMMz+Yp1nyk7PpVlebmEp3ofVCzS7Nt2wI/nVlq7dd5uwsCf8ZoNh2Z9Ch8bCGId9uwxAXjq + 29+/KxSdt52Ttdh/ZKJhUcxPfjg++sh+WQykWFHtiM+zYvKJ37oiUdyGi/dDrXRD1SidJhfLfbz0 + M4vTgmy2H1ySwQnDaxDq1jWbLXnn+fQjareezXOd56rYaEyOyX8AAAD//wMAUtzROh8DAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d6b5b744e034367-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 22 Oct 2024 17:55:16 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '749' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_fbb01161a03eb6f478ff52314b72cfd6 + status: + code: 200 + message: OK +- request: + body: '{"data": {"type": "evaluation_metric", "attributes": {"metrics": [{"span_id": + "6877142543397072040", "trace_id": "6717e70200000000a99ea8ad36f4f36d", "label": + "ragas_faithfulness", "metric_type": "score", "timestamp_ms": 1729619716093, + "score_value": 1.0, "ml_app": "unnamed-ml-app", "tags": ["ddtrace.version:2.15.0.dev219+ge047e25bb.d20241022", + "ml_app:unnamed-ml-app"]}]}}}' + headers: + Content-Type: + - application/json + DD-API-KEY: + - XXXXXX + method: POST + uri: https://api.datad0g.com/api/intake/llm-obs/v1/eval-metric + response: + body: + string: '{"data":{"id":"99fa371c-457c-4d2b-8d4c-61657e0ffd48","type":"evaluation_metric","attributes":{"metrics":[{"id":"CbapxUnzcX","trace_id":"6717e70200000000a99ea8ad36f4f36d","span_id":"6877142543397072040","timestamp_ms":1729619716093,"ml_app":"unnamed-ml-app","metric_type":"score","label":"ragas_faithfulness","score_value":1,"tags":["ddtrace.version:2.15.0.dev219+ge047e25bb.d20241022","ml_app:unnamed-ml-app"]}]}}}' + headers: + content-length: + - '414' + content-security-policy: + - frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pub293163a918901030b79492fe1ab424cf&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatad0g.com + content-type: + - application/vnd.api+json + date: + - Tue, 22 Oct 2024 17:55:17 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + status: + code: 202 + message: Accepted +version: 1 diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_emits_traces.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_emits_traces.yaml new file mode 100644 index 00000000000..8efe7391c90 --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_emits_traces.yaml @@ -0,0 +1,271 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dJHBbtswEETv+gpiz1YgCY7k+JbCKAoEQYKih7SWIdH0SmJDkQR3jbYw + /O8FZcd2D73wMI8znF0eEiFA72ApQA2S1ehN+vipfFytqq+rt7eHX4v9+3NY/aDnL5y9DE/fYRYd + bvsTFX+47pQbvUHWzp6wCigZY2peFVWRLcryfgKj26GJtt5zOnfpqK1Oi6yYp1mV5ouze3BaIcFS + rBMhhDhMZ+xpd/gbliKbfSgjEskeYXm5JAQEZ6ICkkgTS8swu0LlLKOdqrdtuz7UQBgVhc0UX0/5 + ogbScabQEEvGES1TROsavg0olPSapRGuE5+DtAqFJvEqg6a7GjbHTdu2t48G7PYk4+B2b8xZP16m + MK73wW3pzC96p62moQkoydnYmNh5mOgxEWIzbWv/zwLABzd6bti9o42BZZaf8uD6SVdalGfIjqW5 + cRXV/1zNDllqQzc7h1NDbftrQnapOc0J9IcYx6bTtsfggz59QeebfLudl3lZdQ+QHJO/AAAA//8D + AL2Ti/mQAgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c856bee184d42d1-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 20:11:06 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=nMe4XLsotHph1aKmM6xJotYxeBsTIpCG1ULeQ2oiKLc-1727208666-1.0.1.1-eM1elzOCEnpbPLkOO61HSBvaeQPYHEyO4Ba3P2NsxkYV23Fybb7E8tIipei4YDbhyDiLXybnT7H0ETvjbsV89g; + path=/; expires=Tue, 24-Sep-24 20:41:06 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=lKBj.JPFMKz3LiJyz12GeZI73UndAQfhN.5aqiwYPHA-1727208666121-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + openai-organization: + - datadog-staging + openai-processing-ms: + - '576' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_ef3f2830eaf13bceea5db3a7369affda + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=nMe4XLsotHph1aKmM6xJotYxeBsTIpCG1ULeQ2oiKLc-1727208666-1.0.1.1-eM1elzOCEnpbPLkOO61HSBvaeQPYHEyO4Ba3P2NsxkYV23Fybb7E8tIipei4YDbhyDiLXybnT7H0ETvjbsV89g; + _cfuvid=lKBj.JPFMKz3LiJyz12GeZI73UndAQfhN.5aqiwYPHA-1727208666121-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAAwAAAP//dFFBbtswELz7FQuerUByDcnRLS3aS4umSBugSBRINLWSNpFIglwHLgz/ + vaCsSOmhFx5mdoazs6cVgKBa5CBUJ1kNto9uPqY3n+/U3e39/WH49vzQyObn7fevx98v2adErIPC + 7J9R8ZvqSpnB9shk9IVWDiVjcE2yTbaJd2majsRgauyDrLUcbU00kKZoE2+2UZxFyW5Sd4YUepHD + 4woA4DS+Iaeu8ShyiNdvyIDeyxZFPg8BCGf6gAjpPXmWmsV6IZXRjHqMXlXV46kQniXjgJoLkUMh + fnUISlpi2YNp4IuTWiGQhx/Skb8qxBoK4VB6oxfB7BEGJdTkUDE4tMgUaglO3CGQbowb5AhZZ16p + xhpIj9yY7MjTD6/oalJjpuT8VFXV+yUcNgcvQ5H60PcTfp5b6U1rndn7iZ/xhjT5rryEDw14NlaM + 7HkF8DS2f/inUGGdGSyXbF5QB8Ms2138xHL0hf1wPZFsWPYLvkuy/6nKGllS79/dcKqXdLs4xHPM + cU/h/3jGoWxIt+iso8tJG1sm+/02TdKsuRar8+ovAAAA//8DADp8axngAgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8c856bf38f3242d1-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 24 Sep 2024 20:11:06 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + openai-organization: + - datadog-staging + openai-processing-ms: + - '523' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_07733e2c20ff88f138f2ab4cd6a71cc6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation.yaml new file mode 100644 index 00000000000..3eeb5523062 --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation.yaml @@ -0,0 +1,275 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2yRwW7bMBBE7/oKYs9WIKupnPjWSwIEBdIWLVDUMiSKWklsKZLlros0hv+9oKzY + LpILDzOcx53lPhECdAtrAWqQrEZv0g/3z1++qe+PT/hs7340n3+rrOgf8uaj+fMYYBETrvmJil9S + V8qN3iBrZ4+2CigZI3W5ym+KZb66LSZjdC2aGOs9p9cuHbXVaZ7l12m2Spc3c3pwWiHBWmwSIYTY + T2ec07b4BGuRLV6UEYlkj7A+XRICgjNRAUmkiaVlWJxN5SyjnUav63qzL4EwKgqrCV9OfFEC6dgp + VMSScUTLFK1NCV8HFEp6zdII14m7IK1CoUl8kkHTVQnbw7au68tHA3Y7krG43Rkz64dTC+N6H1xD + s3/SO201DVVASc7GiYmdh8k9JEJsp23t/lsA+OBGzxW7X2gjsMiWRx6cP+ns5sVssmNpLlL5avEG + r2qRpTZ0sW9QUg3YnqNZclHu9aNvIY4Fte1fUZKZBPSXGMeq07bH4IM+/mDnK8ybFt+/wwIhOST/ + AAAA//8DAD61UaDPAgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d0b547abf9d4344-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 11 Oct 2024 02:13:17 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=3I.CuJ9tQaqq7Jx.t7O30MCyIVhzXMEkGtUDStmGLFI-1728612797-1.0.1.1-UfFUouoFk6_F_6iTXteAc7boZ2Hybrk5WPrAaOI48QjlyzB6B0hFWImunuI8vYWwm81M.ktGGPKzM_DQVTIhGg; + path=/; expires=Fri, 11-Oct-24 02:43:17 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=0Mm7Wrm.ONaGvoj9p0V0_AxBr0qQwuuVewSp_X52yWU-1728612797380-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '689' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_b91d27a89865f02b1cc739e0f4084331 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=3I.CuJ9tQaqq7Jx.t7O30MCyIVhzXMEkGtUDStmGLFI-1728612797-1.0.1.1-UfFUouoFk6_F_6iTXteAc7boZ2Hybrk5WPrAaOI48QjlyzB6B0hFWImunuI8vYWwm81M.ktGGPKzM_DQVTIhGg; + _cfuvid=0Mm7Wrm.ONaGvoj9p0V0_AxBr0qQwuuVewSp_X52yWU-1728612797380-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2xSy27bMBC86ysWPFuBH6nl6OZLeohRFEbQFo0CiaZW0qYSyZJrI63hfy8oK5aL + 5MLDzM5wdshjBCCoFCkI1UhWnW3j9ee/228/fq7rw/et2WzWyct2sbl/eHz48ntpxSQozO4FFb+p + bpTpbItMRp9p5VAyBtdZMl8tZ/PkLumJzpTYBlltOb41cUea4vl0fhtPk3i2GtSNIYVepPAUAQAc + +zPk1CW+ihSmkzekQ+9ljSK9DAEIZ9qACOk9eZaaxWQkldGMuo9eFMXTMROeJWOHmjORQiYeGwQl + LbFswVRw76RWCOThq3TkbzIxgUw4lN7oUXDxCIMSSnKoGBxaZAq1BCduEEhXxnWyh6wzByqxBNI9 + 1yd75eGGA7qSVJ9pdnouiuJ6CYfV3stQpN637YCfLq20prbO7PzAX/CKNPkmP4cPDXg2VvTsKQJ4 + 7tvf/1eosM50lnM2v1AHwyRZnf3E+Ogju7gbSDYs2xFfzZLJB355iSyp9VfvJ5RUDZajdBpdLff+ + 0o8szguSrt+5RIOT8H88Y5dXpGt01tH5R1Q2x/muxE8LXKKITtE/AAAA//8DADWAJv4fAwAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d0b54801e124344-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 11 Oct 2024 02:13:18 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '1092' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_7cd8e3d5f34a749f0c445fc00f40434e + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages.yaml new file mode 100644 index 00000000000..6d758803679 --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages.yaml @@ -0,0 +1,272 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"What is the capital of + France?\"\nanswer: \"The capital of France is Paris\"\nsentences: \"\"\nanalysis: + \n", "role": "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": + 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2919' + content-type: + - application/json + cookie: + - __cf_bm=3I.CuJ9tQaqq7Jx.t7O30MCyIVhzXMEkGtUDStmGLFI-1728612797-1.0.1.1-UfFUouoFk6_F_6iTXteAc7boZ2Hybrk5WPrAaOI48QjlyzB6B0hFWImunuI8vYWwm81M.ktGGPKzM_DQVTIhGg; + _cfuvid=0Mm7Wrm.ONaGvoj9p0V0_AxBr0qQwuuVewSp_X52yWU-1728612797380-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2yRQW/iMBCF7/kV1pxJFbIUKLdetsetVrS7EkGJcSaJW8e2PMOqu4j/vnJIgaq9 + +PCe3+d540MiBOgaVgJUJ1n13qT3D/9+/taP67d+2XbPT836Zf1w+2v2w/k/rzOYxITbvaDi99SN + cr03yNrZk60CSsZInS7y5XyaL+7uBqN3NZoYaz2nM5f22uo0z/JZmi3S6XJMd04rJFiJTSKEEIfh + jHPaGt9gJbLJu9IjkWwRVudLQkBwJiogiTSxtAyTi6mcZbTD6FVVbQ4FEEZFYTngi4EvCiAdO4WS + WDL2aJmitSlg3aFQ0muWRrhGfA/SKhSaxKMMmm4K2B63VVVdPxqw2ZOMxe3emFE/nlsY1/rgdjT6 + Z73RVlNXBpTkbJyY2HkY3GMixHbY1v7DAsAH13su2b2ijcB5Nj3x4PJJFzefjyY7luYqlS8mX/DK + GllqQ1f7BiVVh/UlmiVX5T4/+hXiVFDb9hMlGUlAf4mxLxttWww+6NMPNr7EfFfj7TecIyTH5D8A + AAD//wMA7Nvjp88CAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d0b5489199a4344-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 11 Oct 2024 02:13:19 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '796' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999323' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_b0f85c6cfb53deb24174225531a6f334 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"The capital of France is Paris.\"\nstatements: \"[\\\"The + capital of France is Paris.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3661' + content-type: + - application/json + cookie: + - __cf_bm=3I.CuJ9tQaqq7Jx.t7O30MCyIVhzXMEkGtUDStmGLFI-1728612797-1.0.1.1-UfFUouoFk6_F_6iTXteAc7boZ2Hybrk5WPrAaOI48QjlyzB6B0hFWImunuI8vYWwm81M.ktGGPKzM_DQVTIhGg; + _cfuvid=0Mm7Wrm.ONaGvoj9p0V0_AxBr0qQwuuVewSp_X52yWU-1728612797380-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.47.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.47.1 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA2ySwW7bMBBE7/qKBc9WIDlO7PgWBEhObYMiQNFGgUWTK2tbiSTItZvG8L8XpFXL + RXLRYYbzuDvUPgMQpMUShGolq951+e3D29fv4eq22D3e332bfS6llk9v/tOP8ov9LSYxYdc/UfG/ + 1IWyveuQyZqjrTxKxkgt59PFdTldFEUyequxi7GN43xm854M5dNiOsuLeV4uhnRrSWEQS3jOAAD2 + 6RvnNBpfxRISKyk9hiA3KJanQwDC2y4qQoZAgaVhMRlNZQ2jSaPXdf28r0Rgydij4UosoRJPLYKS + jlh2YBu499IoBArwKD2Fi0pMoBIeZbBmDJwY8aAETR4Vg0eHTLGWSOIWgUxjfS+T5LzdkUYNZJKX + Jnvl4YYdek0qzVQeXuq6Pl/CY7MNMhZptl036IdTK53dOG/XYfBPekOGQrs6Dh8bCGydSO4hA3hJ + 7W//K1Q4b3vHK7a/0ETgfL448sT46KN7eTOYbFl2o74o55MPeCuNLKkLZ+8nlFQt6jFaZGfLvb/0 + I8RxQTKbd5RsIInwJzD2q4bMBr3zdPwjGrfC6Vrj1SVeo8gO2V8AAAD//wMAMSJDuh8DAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8d0b548f191c4344-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 11 Oct 2024 02:13:20 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '739' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999151' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_6af4350c90271f7b9750cc155145ceb0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/test_llmobs_evaluator_runner.py b/tests/llmobs/test_llmobs_evaluator_runner.py index a846914b3ac..7f7d685cf0a 100644 --- a/tests/llmobs/test_llmobs_evaluator_runner.py +++ b/tests/llmobs/test_llmobs_evaluator_runner.py @@ -79,11 +79,11 @@ def test_evaluator_runner_on_exit(mock_writer_logs, run_python_code_in_subproces pypath.append(env["PYTHONPATH"]) env.update( { - "DD_API_KEY": "dummy-api-key", + "DD_API_KEY": os.getenv("DD_API_KEY", "dummy-api-key"), "DD_SITE": "datad0g.com", "PYTHONPATH": ":".join(pypath), "DD_LLMOBS_ML_APP": "unnamed-ml-app", - "_DD_LLMOBS_WRITER_INTERVAL": "0.01", + "_DD_LLMOBS_EVALUATOR_INTERVAL": "5", } ) out, err, status, pid = run_python_code_in_subprocess( @@ -91,6 +91,7 @@ def test_evaluator_runner_on_exit(mock_writer_logs, run_python_code_in_subproces import os import time import atexit +import mock from ddtrace.llmobs import LLMObs from ddtrace.llmobs._evaluators.runner import EvaluatorRunner from tests.llmobs._utils import logs_vcr @@ -100,13 +101,9 @@ def test_evaluator_runner_on_exit(mock_writer_logs, run_python_code_in_subproces ctx.__enter__() atexit.register(lambda: ctx.__exit__()) LLMObs.enable() -evaluator_runner = EvaluatorRunner( - interval=0.01, llmobs_service=LLMObs -) -evaluator_runner.evaluators.append(DummyEvaluator(llmobs_service=LLMObs)) -evaluator_runner.start() -evaluator_runner.enqueue({"span_id": "123", "trace_id": "1234"}, None) -evaluator_runner.periodic() +LLMObs._instance._evaluator_runner.evaluators.append(DummyEvaluator(llmobs_service=LLMObs)) +LLMObs._instance._evaluator_runner.start() +LLMObs._instance._evaluator_runner.enqueue({"span_id": "123", "trace_id": "1234"}, None) """, env=env, ) diff --git a/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py b/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py new file mode 100644 index 00000000000..51da6aed3cf --- /dev/null +++ b/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py @@ -0,0 +1,200 @@ +import os + +import mock +import pytest + +from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator +from ddtrace.span import Span + +from ._utils import _expected_llmobs_llm_span_event +from ._utils import _expected_ragas_spans +from ._utils import _llm_span_with_expected_ragas_inputs_in_messages +from ._utils import _llm_span_with_expected_ragas_inputs_in_prompt + + +def _llm_span_without_io(): + return _expected_llmobs_llm_span_event(Span("dummy")) + + +def test_ragas_evaluator_init(ragas, LLMObs): + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + assert rf_evaluator.llmobs_service == LLMObs + assert rf_evaluator.ragas_faithfulness_instance == ragas.metrics.faithfulness + assert rf_evaluator.ragas_faithfulness_instance.llm == ragas.llms.llm_factory() + + +def test_ragas_faithfulness_throws_if_dependencies_not_present(LLMObs, mock_ragas_dependencies_not_present, ragas): + with pytest.raises(NotImplementedError, match="Failed to load dependencies for `ragas_faithfulness` evaluator"): + RagasFaithfulnessEvaluator(LLMObs) + + +def test_ragas_faithfulness_returns_none_if_inputs_extraction_fails(ragas, mock_llmobs_submit_evaluation, LLMObs): + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + assert rf_evaluator.evaluate(_llm_span_without_io()) == "fail_extract_faithfulness_inputs" + assert rf_evaluator.llmobs_service.submit_evaluation.call_count == 0 + + +def test_ragas_faithfulness_has_modified_faithfulness_instance( + ragas, mock_llmobs_submit_evaluation, reset_ragas_faithfulness_llm, LLMObs +): + """Faithfulness instance used in ragas evaluator should match the global ragas faithfulness instance""" + from ragas.llms import BaseRagasLLM + from ragas.metrics import faithfulness + + class FirstDummyLLM(BaseRagasLLM): + def __init__(self): + super().__init__() + + def generate_text(self) -> str: + return "dummy llm" + + def agenerate_text(self) -> str: + return "dummy llm" + + faithfulness.llm = FirstDummyLLM() + + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + + assert rf_evaluator.ragas_faithfulness_instance.llm.generate_text() == "dummy llm" + + class SecondDummyLLM(BaseRagasLLM): + def __init__(self): + super().__init__() + + def generate_text(self, statements) -> str: + raise ValueError("dummy_llm") + + def agenerate_text(self, statements) -> str: + raise ValueError("dummy_llm") + + faithfulness.llm = SecondDummyLLM() + + with pytest.raises(ValueError, match="dummy_llm"): + rf_evaluator.evaluate(_llm_span_with_expected_ragas_inputs_in_prompt()) + + +@pytest.mark.vcr_logs +def test_ragas_faithfulness_submits_evaluation(ragas, LLMObs, mock_llmobs_submit_evaluation): + """Test that evaluation is submitted for a valid llm span where question is in the prompt variables""" + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + llm_span = _llm_span_with_expected_ragas_inputs_in_prompt() + rf_evaluator.run_and_submit_evaluation(llm_span) + rf_evaluator.llmobs_service.submit_evaluation.assert_has_calls( + [ + mock.call( + span_context={ + "span_id": llm_span.get("span_id"), + "trace_id": llm_span.get("trace_id"), + }, + label=RagasFaithfulnessEvaluator.LABEL, + metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, + value=1.0, + ) + ] + ) + + +@pytest.mark.vcr_logs +def test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages( + ragas, LLMObs, mock_llmobs_submit_evaluation +): + """Test that evaluation is submitted for a valid llm span where the last message content is the question""" + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + llm_span = _llm_span_with_expected_ragas_inputs_in_messages() + rf_evaluator.run_and_submit_evaluation(llm_span) + rf_evaluator.llmobs_service.submit_evaluation.assert_has_calls( + [ + mock.call( + span_context={ + "span_id": llm_span.get("span_id"), + "trace_id": llm_span.get("trace_id"), + }, + label=RagasFaithfulnessEvaluator.LABEL, + metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, + value=1.0, + ) + ] + ) + + +@pytest.mark.vcr_logs +def test_ragas_faithfulness_emits_traces(ragas, LLMObs): + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + rf_evaluator.evaluate(_llm_span_with_expected_ragas_inputs_in_prompt()) + assert rf_evaluator.llmobs_service._instance._llmobs_span_writer.enqueue.call_count == 7 + calls = rf_evaluator.llmobs_service._instance._llmobs_span_writer.enqueue.call_args_list + + spans = [call[0][0] for call in calls] + + # check name, io, span kinds match + assert spans == _expected_ragas_spans() + + # verify the trace structure + root_span = spans[0] + root_span_id = root_span["span_id"] + assert root_span["parent_id"] == "undefined" + assert root_span["meta"] is not None + assert root_span["meta"]["metadata"] is not None + assert isinstance(root_span["meta"]["metadata"]["faithfulness_list"], list) + assert isinstance(root_span["meta"]["metadata"]["statements"], list) + root_span_trace_id = root_span["trace_id"] + for child_span in spans[1:]: + assert child_span["trace_id"] == root_span_trace_id + + assert spans[1]["parent_id"] == root_span_id # input extraction (task) + assert spans[2]["parent_id"] == root_span_id # create statements (workflow) + assert spans[4]["parent_id"] == root_span_id # create verdicts (workflow) + assert spans[6]["parent_id"] == root_span_id # create score (task) + + assert spans[3]["parent_id"] == spans[2]["span_id"] # create statements prompt (task) + assert spans[5]["parent_id"] == spans[4]["span_id"] # create verdicts prompt (task) + + +def test_llmobs_with_faithfulness_emits_traces_and_evals_on_exit(mock_writer_logs, run_python_code_in_subprocess): + env = os.environ.copy() + pypath = [os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))] + if "PYTHONPATH" in env: + pypath.append(env["PYTHONPATH"]) + env.update( + { + "DD_API_KEY": os.getenv("DD_API_KEY", "dummy-api-key"), + "DD_SITE": "datad0g.com", + "PYTHONPATH": ":".join(pypath), + "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", "dummy-openai-api-key"), + "DD_LLMOBS_ML_APP": "unnamed-ml-app", + "_DD_LLMOBS_EVALUATOR_INTERVAL": "5", + "_DD_LLMOBS_EVALUATORS": "ragas_faithfulness", + "DD_LLMOBS_AGENTLESS_ENABLED": "true", + } + ) + out, err, status, pid = run_python_code_in_subprocess( + """ +import os +import time +import atexit +import mock +from ddtrace.llmobs import LLMObs +from ddtrace.internal.utils.http import Response +from tests.llmobs._utils import _llm_span_with_expected_ragas_inputs_in_messages +from tests.llmobs._utils import logs_vcr + +ctx = logs_vcr.use_cassette( + "tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.emits_traces_and_evaluations_on_exit.yaml" +) +ctx.__enter__() +atexit.register(lambda: ctx.__exit__()) +with mock.patch( + "ddtrace.internal.writer.HTTPWriter._send_payload", + return_value=Response( + status=200, + body="{}", + ), +): + LLMObs.enable() + LLMObs._instance._evaluator_runner.enqueue(_llm_span_with_expected_ragas_inputs_in_messages(), None) +""", + env=env, + ) + assert status == 0, err + assert out == b"" + assert err == b"" diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index f174b2c219a..303dcb78865 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1,6 +1,7 @@ import json import os import threading +import time import mock import pytest @@ -25,6 +26,7 @@ from ddtrace.llmobs._constants import OUTPUT_MESSAGES from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import PROPAGATED_PARENT_ID_KEY +from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING @@ -1727,6 +1729,37 @@ def test_llmobs_fork_disabled_then_enabled(monkeypatch): svc.disable() +def test_llmobs_with_evaluator_runner(LLMObs, mock_llmobs_evaluator_runner): + with LLMObs.llm(model_name="test_model"): + pass + time.sleep(0.1) + assert LLMObs._instance._evaluator_runner.enqueue.call_count == 1 + + +def test_llmobs_with_evaluator_runner_does_not_enqueue_evaluation_spans(mock_llmobs_evaluator_runner, LLMObs): + with LLMObs.llm(model_name="test_model", ml_app="{}-dummy".format(RAGAS_ML_APP_PREFIX)): + pass + time.sleep(0.1) + assert LLMObs._instance._evaluator_runner.enqueue.call_count == 0 + + +def test_llmobs_with_evaluation_runner_does_not_enqueue_non_llm_spans(mock_llmobs_evaluator_runner, LLMObs): + with LLMObs.workflow(name="test"): + pass + with LLMObs.agent(name="test"): + pass + with LLMObs.task(name="test"): + pass + with LLMObs.embedding(model_name="test"): + pass + with LLMObs.retrieval(name="test"): + pass + with LLMObs.tool(name="test"): + pass + time.sleep(0.1) + assert LLMObs._instance._evaluator_runner.enqueue.call_count == 0 + + def test_annotation_context_modifies_span_tags(LLMObs): with LLMObs.annotation_context(tags={"foo": "bar"}): with LLMObs.agent(name="test_agent") as span: diff --git a/tests/llmobs/test_llmobs_trace_processor.py b/tests/llmobs/test_llmobs_trace_processor.py index da1544c5e66..bfbb17cdd52 100644 --- a/tests/llmobs/test_llmobs_trace_processor.py +++ b/tests/llmobs/test_llmobs_trace_processor.py @@ -155,9 +155,9 @@ def test_session_id_propagates_ignore_non_llmobs_spans(): with dummy_tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: great_grandchild_span.set_tag_str(SPAN_KIND, "llm") tp = LLMObsTraceProcessor(dummy_tracer._writer) - llm_span_event = tp._llmobs_span_event(llm_span) - grandchild_span_event = tp._llmobs_span_event(grandchild_span) - great_grandchild_span_event = tp._llmobs_span_event(great_grandchild_span) + llm_span_event, _ = tp._llmobs_span_event(llm_span) + grandchild_span_event, _ = tp._llmobs_span_event(grandchild_span) + great_grandchild_span_event, _ = tp._llmobs_span_event(great_grandchild_span) assert llm_span_event["session_id"] == "session-123" assert grandchild_span_event["session_id"] == "session-123" assert great_grandchild_span_event["session_id"] == "session-123" @@ -171,7 +171,7 @@ def test_ml_app_tag_defaults_to_env_var(): llm_span.set_tag_str(SPAN_KIND, "llm") pass tp = LLMObsTraceProcessor(dummy_tracer._writer) - span_event = tp._llmobs_span_event(llm_span) + span_event, _ = tp._llmobs_span_event(llm_span) assert "ml_app:" in span_event["tags"] @@ -183,7 +183,7 @@ def test_ml_app_tag_overrides_env_var(): llm_span.set_tag_str(SPAN_KIND, "llm") llm_span.set_tag(ML_APP, "test-ml-app") tp = LLMObsTraceProcessor(dummy_tracer._writer) - span_event = tp._llmobs_span_event(llm_span) + span_event, _ = tp._llmobs_span_event(llm_span) assert "ml_app:test-ml-app" in span_event["tags"] @@ -203,9 +203,9 @@ def test_ml_app_propagates_ignore_non_llmobs_spans(): with dummy_tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: great_grandchild_span.set_tag_str(SPAN_KIND, "llm") tp = LLMObsTraceProcessor(dummy_tracer._writer) - llm_span_event = tp._llmobs_span_event(llm_span) - grandchild_span_event = tp._llmobs_span_event(grandchild_span) - great_grandchild_span_event = tp._llmobs_span_event(great_grandchild_span) + llm_span_event, _ = tp._llmobs_span_event(llm_span) + grandchild_span_event, _ = tp._llmobs_span_event(grandchild_span) + great_grandchild_span_event, _ = tp._llmobs_span_event(great_grandchild_span) assert "ml_app:test-ml-app" in llm_span_event["tags"] assert "ml_app:test-ml-app" in grandchild_span_event["tags"] assert "ml_app:test-ml-app" in great_grandchild_span_event["tags"] @@ -236,7 +236,7 @@ def test_model_and_provider_are_set(): llm_span.set_tag(MODEL_NAME, "model_name") llm_span.set_tag(MODEL_PROVIDER, "model_provider") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event = tp._llmobs_span_event(llm_span) + span_event, _ = tp._llmobs_span_event(llm_span) assert span_event["meta"]["model_name"] == "model_name" assert span_event["meta"]["model_provider"] == "model_provider" @@ -250,7 +250,7 @@ def test_model_provider_defaults_to_custom(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(MODEL_NAME, "model_name") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event = tp._llmobs_span_event(llm_span) + span_event, _ = tp._llmobs_span_event(llm_span) assert span_event["meta"]["model_name"] == "model_name" assert span_event["meta"]["model_provider"] == "custom" @@ -264,7 +264,7 @@ def test_model_not_set_if_not_llm_kind_span(): span.set_tag(SPAN_KIND, "workflow") span.set_tag(MODEL_NAME, "model_name") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event = tp._llmobs_span_event(span) + span_event, _ = tp._llmobs_span_event(span) assert "model_name" not in span_event["meta"] assert "model_provider" not in span_event["meta"] @@ -278,7 +278,9 @@ def test_input_messages_are_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(INPUT_MESSAGES, '[{"content": "message", "role": "user"}]') tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["input"]["messages"] == [{"content": "message", "role": "user"}] + assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["messages"] == [ + {"content": "message", "role": "user"} + ] def test_input_value_is_set(): @@ -290,7 +292,7 @@ def test_input_value_is_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(INPUT_VALUE, "value") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["input"]["value"] == "value" + assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["value"] == "value" def test_input_parameters_are_set(): @@ -302,7 +304,7 @@ def test_input_parameters_are_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(INPUT_PARAMETERS, '{"key": "value"}') tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["input"]["parameters"] == {"key": "value"} + assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["parameters"] == {"key": "value"} def test_output_messages_are_set(): @@ -314,7 +316,9 @@ def test_output_messages_are_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(OUTPUT_MESSAGES, '[{"content": "message", "role": "user"}]') tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["output"]["messages"] == [{"content": "message", "role": "user"}] + assert tp._llmobs_span_event(llm_span)[0]["meta"]["output"]["messages"] == [ + {"content": "message", "role": "user"} + ] def test_output_value_is_set(): @@ -326,7 +330,7 @@ def test_output_value_is_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(OUTPUT_VALUE, "value") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["output"]["value"] == "value" + assert tp._llmobs_span_event(llm_span)[0]["meta"]["output"]["value"] == "value" def test_prompt_is_set(): @@ -338,7 +342,7 @@ def test_prompt_is_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(INPUT_PROMPT, json.dumps({"variables": {"var1": "var2"}})) tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["input"]["prompt"] == {"variables": {"var1": "var2"}} + assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["prompt"] == {"variables": {"var1": "var2"}} def test_prompt_is_not_set_for_non_llm_spans(): @@ -351,7 +355,7 @@ def test_prompt_is_not_set_for_non_llm_spans(): task_span.set_tag(INPUT_VALUE, "ival") task_span.set_tag(INPUT_PROMPT, json.dumps({"variables": {"var1": "var2"}})) tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(task_span)["meta"]["input"].get("prompt") is None + assert tp._llmobs_span_event(task_span)[0]["meta"]["input"].get("prompt") is None def test_metadata_is_set(): @@ -363,7 +367,7 @@ def test_metadata_is_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(METADATA, '{"key": "value"}') tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["meta"]["metadata"] == {"key": "value"} + assert tp._llmobs_span_event(llm_span)[0]["meta"]["metadata"] == {"key": "value"} def test_metrics_are_set(): @@ -375,7 +379,7 @@ def test_metrics_are_set(): llm_span.set_tag(SPAN_KIND, "llm") llm_span.set_tag(METRICS, '{"tokens": 100}') tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["metrics"] == {"tokens": 100} + assert tp._llmobs_span_event(llm_span)[0]["metrics"] == {"tokens": 100} def test_langchain_span_name_is_set_to_class_name(): @@ -386,7 +390,7 @@ def test_langchain_span_name_is_set_to_class_name(): with dummy_tracer.trace(LANGCHAIN_APM_SPAN_NAME, resource="expected_name", span_type=SpanTypes.LLM) as llm_span: llm_span.set_tag(SPAN_KIND, "llm") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)["name"] == "expected_name" + assert tp._llmobs_span_event(llm_span)[0]["name"] == "expected_name" def test_error_is_set(): @@ -399,7 +403,7 @@ def test_error_is_set(): llm_span.set_tag(SPAN_KIND, "llm") raise ValueError("error") tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event = tp._llmobs_span_event(llm_span) + span_event, _ = tp._llmobs_span_event(llm_span) assert span_event["meta"]["error.message"] == "error" assert "ValueError" in span_event["meta"]["error.type"] assert 'raise ValueError("error")' in span_event["meta"]["error.stack"] From c37939f43e100d1a1e8bd941806f132dc28f487c Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:09:27 +0100 Subject: [PATCH 090/372] chore(asm): update security rules to 1.13.2 (#11227) Update security rules to [1.13.2](https://github.com/DataDog/appsec-event-rules/releases/tag/1.13.2) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/rules.json | 6 ++---- tests/appsec/integrations/pygoat_tests/test_pygoat.py | 1 + ...t_processor.test_appsec_body_no_collection_snapshot.json | 2 +- ...rocessor.test_appsec_cookies_no_collection_snapshot.json | 2 +- ...ppsec.test_processor.test_appsec_span_tags_snapshot.json | 2 +- ...go.test_django_appsec_snapshots.test_appsec_enabled.json | 2 +- ..._django_appsec_snapshots.test_appsec_enabled_attack.json | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ddtrace/appsec/rules.json b/ddtrace/appsec/rules.json index a2a52ac8988..d0e486c6731 100644 --- a/ddtrace/appsec/rules.json +++ b/ddtrace/appsec/rules.json @@ -1,7 +1,7 @@ { "version": "2.2", "metadata": { - "rules_version": "1.13.1" + "rules_version": "1.13.2" }, "rules": [ { @@ -6335,7 +6335,6 @@ { "id": "rasp-934-100", "name": "Server-side request forgery exploit", - "enabled": false, "tags": { "type": "ssrf", "category": "vulnerability_trigger", @@ -6384,7 +6383,6 @@ { "id": "rasp-942-100", "name": "SQL injection exploit", - "enabled": false, "tags": { "type": "sql_injection", "category": "vulnerability_trigger", @@ -6424,7 +6422,7 @@ } ] }, - "operator": "sqli_detector" + "operator": "sqli_detector@v2" } ], "transformers": [], diff --git a/tests/appsec/integrations/pygoat_tests/test_pygoat.py b/tests/appsec/integrations/pygoat_tests/test_pygoat.py index 13e8eb9d23f..2be426cd37e 100644 --- a/tests/appsec/integrations/pygoat_tests/test_pygoat.py +++ b/tests/appsec/integrations/pygoat_tests/test_pygoat.py @@ -130,6 +130,7 @@ def test_cmdi(client): assert vulnerability_in_traces("COMMAND_INJECTION", client.agent_session) +@pytest.mark.skip("TODO: fix interaction with new RASP rules") def test_sqli(client): payload = {"name": "admin", "pass": "anything' OR '1' ='1", "csrfmiddlewaretoken": client.csrftoken} reply = client.pygoat_session.post(PYGOAT_URL + "/sql_lab", data=payload, headers=TESTAGENT_HEADERS) diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index b4e68b2b540..0fcdb60d1e3 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -8,7 +8,7 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.1", + "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.waf.version": "1.20.1", "_dd.origin": "appsec", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index 2aa9610cccf..28175b2530a 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -8,7 +8,7 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.1", + "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.waf.version": "1.20.1", "_dd.origin": "appsec", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index 0bbc13be00e..86780e6d037 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -8,7 +8,7 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.1", + "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index 0d8f4ab4d8d..b1a8a8b0bd4 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.appsec.event_rules.version": "1.13.1", + "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", "_dd.p.dm": "-0", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index 99e43862127..2da6e3029aa 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.appsec.event_rules.version": "1.13.1", + "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.waf.version": "1.20.1", "_dd.base_service": "", From e6dc6fb2f4f62733a0bc392308dd505f58bc0474 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 30 Oct 2024 19:35:50 +0000 Subject: [PATCH 091/372] test(di): check tags against clean environment (#11190) We improve the tags testing by checking against a clean environment. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/debugging/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index 7f7d9d6d8ac..49ca4966d65 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -12,7 +12,7 @@ @contextmanager def debugger_config(**kwargs): - with override_env(kwargs): + with override_env(kwargs, replace_os_env=True): from ddtrace.settings import Config import ddtrace.settings.dynamic_instrumentation From 92f3311d70f3c846301f8e8d7d39aa4f11ffaf1f Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Thu, 31 Oct 2024 13:26:37 +0100 Subject: [PATCH 092/372] chore: add debugger scenarios in system-tests ci (#11237) Recently, several error has been introduced in debuggers features, those errros has been caught by system-tests nightlies. By adding those scenario in the dd-trace-py's CI, we'll prevent those errors to reach `main` branch. Needs DataDog/system-tests#3354 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/system-tests.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 5b3ee98cfed..2df85e4d6ea 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -94,7 +94,7 @@ jobs: strategy: matrix: weblog-variant: [flask-poc, uwsgi-poc , django-poc, fastapi, python3.12] - scenario: [remote-config, appsec, appsec-1, other] + scenario: [remote-config, appsec, appsec-1, other, debugger-1, debugger-2] fail-fast: false env: @@ -221,6 +221,34 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' run: ./run.sh APPSEC_RASP + - name: Run DEBUGGER_PROBES_STATUS + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_PROBES_STATUS + + - name: Run DEBUGGER_METHOD_PROBES_SNAPSHOT + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_METHOD_PROBES_SNAPSHOT + + - name: Run DEBUGGER_LINE_PROBES_SNAPSHOT + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_LINE_PROBES_SNAPSHOT + + - name: Run DEBUGGER_MIX_LOG_PROBE + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_MIX_LOG_PROBE + + - name: Run DEBUGGER_PII_REDACTION + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_PII_REDACTION + + - name: Run DEBUGGER_EXPRESSION_LANGUAGE + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' + run: ./run.sh DEBUGGER_EXPRESSION_LANGUAGE + + - name: Run DEBUGGER_EXCEPTION_REPLAY + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-2' + run: ./run.sh DEBUGGER_EXCEPTION_REPLAY + # The compress step speed up a lot the upload artifact process - name: Compress artifact if: always() && steps.docker_load.outcome == 'success' From e33e2355e97b2bbd40d4223bb6d0c79f8da5b8a4 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 31 Oct 2024 15:20:31 +0100 Subject: [PATCH 093/372] fix(iast): add google.auth to the IAST denylist (#11240) This fix resolves an issue where importing the ``google.cloud.storage.batch`` module would fail raising an AttributeError ``` File "site-packages/google/auth/crypt/rsa.py", line 22, in RSASigner = _cryptography_rsa.RSASigner ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: module 'google.auth.crypt._cryptography_rsa' has no attribute 'RSASigner' ``` tests_packages are working in this branch: https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/76358/workflows/427a6bbc-f088-4779-b6fd-35e8e6f988d5/jobs/4346061 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_ast/ast_patching.py | 1 + .../notes/iast-fi-import-error-google-37815bda58036c08.yaml | 4 ++++ tests/appsec/iast_packages/packages/pkg_google_api_core.py | 6 ++++++ tests/appsec/iast_packages/test_packages.py | 6 ++++-- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/iast-fi-import-error-google-37815bda58036c08.yaml diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 572fca02ce6..22494c7da52 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -300,6 +300,7 @@ "uvicorn.", "anyio.", "httpcore.", + "google.auth.crypt.", ) diff --git a/releasenotes/notes/iast-fi-import-error-google-37815bda58036c08.yaml b/releasenotes/notes/iast-fi-import-error-google-37815bda58036c08.yaml new file mode 100644 index 00000000000..d27e37136a0 --- /dev/null +++ b/releasenotes/notes/iast-fi-import-error-google-37815bda58036c08.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: This fix resolves an issue where importing the ``google.cloud.storage.batch`` module would fail raising an ImportError diff --git a/tests/appsec/iast_packages/packages/pkg_google_api_core.py b/tests/appsec/iast_packages/packages/pkg_google_api_core.py index 81d97307c00..a87e0ae3456 100644 --- a/tests/appsec/iast_packages/packages/pkg_google_api_core.py +++ b/tests/appsec/iast_packages/packages/pkg_google_api_core.py @@ -9,6 +9,12 @@ from .utils import ResultResponse +try: + from google.cloud.storage.batch import Batch # noqa:F401 +except ModuleNotFoundError: + pass + + pkg_google_api_core = Blueprint("package_google_api_core", __name__) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 66a50b5f7b1..cde0bf5a89c 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -268,7 +268,7 @@ def uninstall(self, python_cmd): PackageForTesting("fsspec", "2024.5.0", "", "/", ""), PackageForTesting( "google-auth", - "2.29.0", + "2.35.0", "", "", "", @@ -278,12 +278,14 @@ def uninstall(self, python_cmd): ), PackageForTesting( "google-api-core", - "2.19.0", + "2.22.0", "", "", "", import_name="google", import_module_to_validate="google.auth.iam", + extras=[("google-cloud-storage", "2.18.2")], + test_e2e=True, ), PackageForTesting( "google-api-python-client", From addeccdc4641b122036b7fc046f720d7e617b50a Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:22:36 +0000 Subject: [PATCH 094/372] chore(ci_visibility): add Auto Test Retries to pytest plugin v2 (#11241) Introduces support for Auto Test Retries to the v2 `pytest` plugin. This retries tests that have failed their first attempt (except for tests that have failed setup or teardown, which are not retried at all). Failed tests that pass retries result in the session passing (with `pytest` exiting with code 0). Test that fail all retries cause the session to fail (exiting with code 1). If any tests were retried, a section is added to the final summary report, showing the results: ``` ======================= Datadog Auto Test Retries ======================== _________________________________ FAILED _________________________________ FAILED tests/test_some_fail.py::test_func_fails_all_retries _________________________________ PASSED _________________________________ PASSED tests/test_all_pass.py::TestAllPass::test_method_succeeds_on_5th_retry PASSED tests/test_all_pass.py::TestAllPass::test_method_succeeds_on_1st_retry PASSED tests/test_all_pass.py::test_func_succeeds_on_3rd PASSED tests/test_all_pass.py::test_func_succeeds_first_retry PASSED tests/test_some_fail.py::TestAllPass::test_method_succeeds_on_5th_retry PASSED tests/test_some_fail.py::TestAllPass::test_method_succeeds_on_1st_retry PASSED tests/test_some_fail.py::test_func_succeeds_on_3rd PASSED tests/test_some_fail.py::test_func_succeeds_first_retry ___________________ Datadog Auto Test Retries summary ____________________ 1 failed, 8 passed (total attempts: 26 failed, 8 passed) ========================================================================== ``` Other changes in this PR: - Some refactoring to DRY `pytest` retry logic - Introduction of API to "stash" data on Test Visibility objects - Tweak to ATR logic to make successfully-retried test not affect suite-level-or-above status - Tweak to telemetry to add `is_retry` tag to `test`-level `event_finished` metric Finally, no release note is added as the feature is still unreleased. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_atr_utils.py | 274 ++++++ ddtrace/contrib/pytest/_efd_utils.py | 88 +- ddtrace/contrib/pytest/_plugin_v2.py | 74 +- ddtrace/contrib/pytest/_retry_utils.py | 61 +- ddtrace/contrib/pytest/_utils.py | 5 + ddtrace/contrib/pytest/constants.py | 1 + ddtrace/internal/ci_visibility/api/_base.py | 18 + .../internal/ci_visibility/api/_session.py | 12 + ddtrace/internal/ci_visibility/api/_test.py | 10 + ddtrace/internal/ci_visibility/recorder.py | 27 + .../ci_visibility/telemetry/events.py | 14 +- .../internal/test_visibility/_atr_mixins.py | 10 + ddtrace/internal/test_visibility/api.py | 20 + tests/contrib/pytest/test_pytest.py | 5 +- tests/contrib/pytest/test_pytest_atr.py | 244 +++++ tests/contrib/pytest/test_pytest_efd.py | 2 +- ...ers.test_manual_api_fake_atr_mix_fail.json | 872 +++++++++--------- ...ers.test_manual_api_fake_atr_mix_pass.json | 477 +++++----- 18 files changed, 1464 insertions(+), 750 deletions(-) create mode 100644 ddtrace/contrib/pytest/_atr_utils.py create mode 100644 tests/contrib/pytest/test_pytest_atr.py diff --git a/ddtrace/contrib/pytest/_atr_utils.py b/ddtrace/contrib/pytest/_atr_utils.py new file mode 100644 index 00000000000..4ccaf554339 --- /dev/null +++ b/ddtrace/contrib/pytest/_atr_utils.py @@ -0,0 +1,274 @@ +import typing as t + +import _pytest +import pytest + +from ddtrace.contrib.pytest._retry_utils import RetryOutcomes +from ddtrace.contrib.pytest._retry_utils import _get_outcome_from_retry +from ddtrace.contrib.pytest._retry_utils import _get_retry_attempt_string +from ddtrace.contrib.pytest._retry_utils import set_retry_num +from ddtrace.contrib.pytest._types import pytest_TestReport +from ddtrace.contrib.pytest._types import pytest_TestShortLogReport +from ddtrace.contrib.pytest._utils import PYTEST_STATUS +from ddtrace.contrib.pytest._utils import _get_test_id_from_item +from ddtrace.contrib.pytest._utils import _TestOutcome +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId +from ddtrace.internal.test_visibility.api import InternalTest + + +log = get_logger(__name__) + + +class _ATR_RETRY_OUTCOMES: + ATR_ATTEMPT_PASSED = "dd_atr_attempt_passed" + ATR_ATTEMPT_FAILED = "dd_atr_attempt_failed" + ATR_ATTEMPT_SKIPPED = "dd_atr_attempt_skipped" + ATR_FINAL_PASSED = "dd_atr_final_passed" + ATR_FINAL_FAILED = "dd_atr_final_failed" + + +_FINAL_OUTCOMES: t.Dict[TestStatus, str] = { + TestStatus.PASS: _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, + TestStatus.FAIL: _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, +} + + +def atr_handle_retries( + test_id: InternalTestId, + item: pytest.Item, + when: str, + original_result: pytest_TestReport, + test_outcome: _TestOutcome, +): + # Overwrite the original result to avoid double-counting when displaying totals in final summary + if when == "call": + if test_outcome.status == TestStatus.FAIL: + original_result.outcome = _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED + return + if InternalTest.get_tag(test_id, "_dd.ci.atr_setup_failed"): + log.debug("Test item %s failed during setup, will not be retried for Early Flake Detection") + return + if InternalTest.get_tag(test_id, "_dd.ci.atr_teardown_failed"): + # NOTE: tests that passed their call but failed during teardown are not retried + log.debug("Test item %s failed during teardown, will not be retried for Early Flake Detection") + return + + atr_outcome = _atr_do_retries(item) + + final_report = pytest_TestReport( + nodeid=item.nodeid, + location=item.location, + keywords=item.keywords, + when="call", + longrepr=None, + outcome=_FINAL_OUTCOMES[atr_outcome], + ) + item.ihook.pytest_runtest_logreport(report=final_report) + + +def atr_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) -> t.List[pytest_TestReport]: + return terminalreporter.getreports(_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED) + + +def _atr_do_retries(item: pytest.Item) -> TestStatus: + outcomes = RetryOutcomes( + PASSED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, + FAILED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, + SKIPPED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, + XFAIL=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, + XPASS=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, + ) + + test_id = _get_test_id_from_item(item) + + while InternalTest.atr_should_retry(test_id): + retry_num = InternalTest.atr_add_retry(test_id, start_immediately=True) + + with set_retry_num(item.nodeid, retry_num): + retry_outcome = _get_outcome_from_retry(item, outcomes) + + InternalTest.atr_finish_retry( + test_id, retry_num, retry_outcome.status, retry_outcome.skip_reason, retry_outcome.exc_info + ) + + return InternalTest.atr_get_final_status(test_id) + + +def _atr_write_report_for_status( + terminalreporter: _pytest.terminal.TerminalReporter, + status_key: str, + status_text: str, + report_outcome: str, + raw_strings: t.List[str], + markedup_strings: t.List[str], + color: str, + delete_reports: bool = True, +): + reports = terminalreporter.getreports(status_key) + markup_kwargs = {color: True} + if reports: + text = f"{len(reports)} {status_text}" + raw_strings.append(text) + markedup_strings.append(terminalreporter._tw.markup(text, **markup_kwargs, bold=True)) + terminalreporter.write_sep("_", status_text.upper(), **markup_kwargs, bold=True) + for report in reports: + line = f"{terminalreporter._tw.markup(status_text.upper(), **markup_kwargs)} {report.nodeid}" + terminalreporter.write_line(line) + report.outcome = report_outcome + # Do not re-append a report if a report already exists for the item in the reports + for existing_reports in terminalreporter.stats.get(report_outcome, []): + if existing_reports.nodeid == report.nodeid: + break + else: + terminalreporter.stats.setdefault(report_outcome, []).append(report) + if delete_reports: + del terminalreporter.stats[status_key] + + +def _atr_prepare_attempts_strings( + terminalreporter: _pytest.terminal.TerminalReporter, + reports_key: str, + reports_text: str, + raw_strings: t.List[str], + markedup_strings: t.List[str], + color: str, + bold: bool = False, +): + reports = terminalreporter.getreports(reports_key) + markup_kwargs = {color: True} + if bold: + markup_kwargs["bold"] = True + if reports: + failed_attempts_text = f"{len(reports)} {reports_text}" + raw_strings.append(failed_attempts_text) + markedup_strings.append(terminalreporter._tw.markup(failed_attempts_text, **markup_kwargs)) + del terminalreporter.stats[reports_key] + + +def atr_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.TerminalReporter): + # When there were no ATR attempts to retry tests, there is no need to report anything, but just in case, we clear + # out any potential leftover data: + if not terminalreporter.stats.get(_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED): + return + + terminalreporter.write_sep("=", "Datadog Auto Test Retries", purple=True, bold=True) + # Print summary info + raw_summary_strings = [] + markedup_summary_strings = [] + + _atr_write_report_for_status( + terminalreporter, + _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, + "failed", + PYTEST_STATUS.FAILED, + raw_summary_strings, + markedup_summary_strings, + color="red", + ) + + _atr_write_report_for_status( + terminalreporter, + _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, + "passed", + PYTEST_STATUS.PASSED, + raw_summary_strings, + markedup_summary_strings, + color="green", + ) + + raw_attempt_strings = [] + markedup_attempts_strings = [] + + _atr_prepare_attempts_strings( + terminalreporter, + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, + "failed", + raw_attempt_strings, + markedup_attempts_strings, + "red", + bold=True, + ) + _atr_prepare_attempts_strings( + terminalreporter, + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, + "passed", + raw_attempt_strings, + markedup_attempts_strings, + "green", + ) + _atr_prepare_attempts_strings( + terminalreporter, + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, + "skipped", + raw_attempt_strings, + markedup_attempts_strings, + "yellow", + ) + + raw_summary_string = ". ".join(raw_summary_strings) + # NOTE: find out why bold=False seems to apply to the following string, rather than the current one... + markedup_summary_string = ", ".join(markedup_summary_strings) + + if markedup_attempts_strings: + markedup_summary_string += ( + terminalreporter._tw.markup(" (total attempts: ", purple=True) + + ", ".join(markedup_attempts_strings) + + terminalreporter._tw.markup(")", purple=True) + ) + raw_summary_string += f" (total attempts: {', '.join(raw_attempt_strings)})" + + markedup_summary_string += terminalreporter._tw.markup("", purple=True, bold=True) + if markedup_summary_string.endswith("\x1b[0m"): + markedup_summary_string = markedup_summary_string[:-4] + + # Print summary counts + terminalreporter.write_sep("_", "Datadog Auto Test Retries summary", purple=True, bold=True) + + if raw_summary_string: + terminalreporter.write_sep( + " ", + markedup_summary_string, + fullwidth=terminalreporter._tw.fullwidth + (len(markedup_summary_string) - len(raw_summary_string)), + purple=True, + bold=True, + ) + else: + terminalreporter.write_sep( + " ", + "No tests were retried.", + purple=True, + bold=True, + ) + terminalreporter.write_sep("=", purple=True, bold=True) + + +def atr_get_teststatus(report: pytest_TestReport) -> t.Optional[pytest_TestShortLogReport]: + if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED: + return pytest.TestShortLogReport( + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, + "r", + (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}PASSED", {"green": True}), + ) + if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED: + return pytest.TestShortLogReport( + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, + "R", + (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}FAILED", {"yellow": True}), + ) + if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED: + return pytest.TestShortLogReport( + _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, + "s", + (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), + ) + if report.outcome == _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED: + return pytest.TestShortLogReport( + _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, ".", ("ATR FINAL STATUS: PASSED", {"green": True}) + ) + if report.outcome == _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED: + return pytest.TestShortLogReport( + _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, "F", ("ATR FINAL STATUS: FAILED", {"red": True}) + ) + return None diff --git a/ddtrace/contrib/pytest/_efd_utils.py b/ddtrace/contrib/pytest/_efd_utils.py index 3a981ab48cd..ad94adec619 100644 --- a/ddtrace/contrib/pytest/_efd_utils.py +++ b/ddtrace/contrib/pytest/_efd_utils.py @@ -1,20 +1,17 @@ import typing as t import _pytest -from _pytest.logging import caplog_handler_key -from _pytest.logging import caplog_records_key import pytest from ddtrace.contrib.pytest._retry_utils import RetryOutcomes -from ddtrace.contrib.pytest._retry_utils import _efd_get_attempt_string -from ddtrace.contrib.pytest._retry_utils import _retry_run_when +from ddtrace.contrib.pytest._retry_utils import _get_outcome_from_retry +from ddtrace.contrib.pytest._retry_utils import _get_retry_attempt_string from ddtrace.contrib.pytest._retry_utils import set_retry_num from ddtrace.contrib.pytest._types import pytest_TestReport from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_test_id_from_item from ddtrace.contrib.pytest._utils import _TestOutcome -from ddtrace.ext.test_visibility.api import TestExcInfo from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal.logger import get_logger from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus @@ -87,7 +84,7 @@ def efd_handle_retries( ) InternalTest.mark_skip(test_id) - efd_outcome = _efd_handle_retries(item) + efd_outcome = _efd_do_retries(item) final_report = pytest_TestReport( nodeid=item.nodeid, @@ -104,14 +101,22 @@ def efd_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) return terminalreporter.getreports(_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED) -def _efd_handle_retries(item: pytest.Item) -> EFDTestStatus: +def _efd_do_retries(item: pytest.Item) -> EFDTestStatus: test_id = _get_test_id_from_item(item) + outcomes = RetryOutcomes( + PASSED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + FAILED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + SKIPPED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, + XFAIL=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, + XPASS=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, + ) + while InternalTest.efd_should_retry(test_id): retry_num = InternalTest.efd_add_retry(test_id, start_immediately=True) with set_retry_num(item.nodeid, retry_num): - retry_outcome = _efd_get_outcome_from_item(item) + retry_outcome = _get_outcome_from_retry(item, outcomes) InternalTest.efd_finish_retry( test_id, retry_num, retry_outcome.status, retry_outcome.skip_reason, retry_outcome.exc_info @@ -120,67 +125,6 @@ def _efd_handle_retries(item: pytest.Item) -> EFDTestStatus: return InternalTest.efd_get_final_status(test_id) -def _efd_get_outcome_from_item( - item: pytest.Item, -) -> _TestOutcome: - _outcome_status: t.Optional[TestStatus] = None - _outcome_skip_reason: t.Optional[str] = None - _outcome_exc_info: t.Optional[TestExcInfo] = None - - # _initrequest() needs to be called first because the test has already executed once - item._initrequest() - - outcomes = RetryOutcomes( - PASSED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, - FAILED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, - SKIPPED=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, - XFAIL=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, - XPASS=_EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, - ) - - # Setup - setup_call, setup_report = _retry_run_when(item, "setup", outcomes) - if setup_report.failed: - _outcome_status = TestStatus.FAIL - if setup_call.excinfo is not None: - _outcome_exc_info = TestExcInfo(setup_call.excinfo.type, setup_call.excinfo.value, setup_call.excinfo.tb) - item.stash[caplog_records_key] = {} - item.stash[caplog_handler_key] = {} - if setup_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: - _outcome_status = TestStatus.SKIP - - # Call - if setup_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: - call_call, call_report = _retry_run_when(item, "call", outcomes) - if call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: - _outcome_status = TestStatus.FAIL - if call_call.excinfo is not None: - _outcome_exc_info = TestExcInfo(call_call.excinfo.type, call_call.excinfo.value, call_call.excinfo.tb) - item.stash[caplog_records_key] = {} - item.stash[caplog_handler_key] = {} - elif call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: - _outcome_status = TestStatus.SKIP - elif call_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: - _outcome_status = TestStatus.PASS - - # Teardown does not happen if setup skipped - if not setup_report.skipped: - teardown_call, teardown_report = _retry_run_when(item, "teardown", outcomes) - # Only override the outcome if the teardown failed, otherwise defer to either setup or call outcome - if teardown_report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: - _outcome_status = TestStatus.FAIL - if teardown_call.excinfo is not None: - _outcome_exc_info = TestExcInfo( - teardown_call.excinfo.type, teardown_call.excinfo.value, teardown_call.excinfo.tb - ) - item.stash[caplog_records_key] = {} - item.stash[caplog_handler_key] = {} - - item._initrequest() - - return _TestOutcome(status=_outcome_status, skip_reason=_outcome_skip_reason, exc_info=_outcome_exc_info) - - def _efd_write_report_for_status( terminalreporter: _pytest.terminal.TerminalReporter, status_key: str, @@ -365,19 +309,19 @@ def efd_get_teststatus(report: pytest_TestReport) -> t.Optional[pytest_TestShort return pytest.TestShortLogReport( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, "r", - (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}PASSED", {"green": True}), + (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}PASSED", {"green": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: return pytest.TestShortLogReport( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, "R", - (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}FAILED", {"yellow": True}), + (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}FAILED", {"yellow": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: return pytest.TestShortLogReport( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, "s", - (f"EFD RETRY {_efd_get_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), + (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED: return pytest.TestShortLogReport( diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 922f987d3e0..85b8399db0a 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -11,6 +11,10 @@ from ddtrace.contrib.internal.coverage.patch import run_coverage_report from ddtrace.contrib.internal.coverage.utils import _is_coverage_invoked_by_coverage_run from ddtrace.contrib.internal.coverage.utils import _is_coverage_patched +from ddtrace.contrib.pytest._atr_utils import atr_get_failed_reports +from ddtrace.contrib.pytest._atr_utils import atr_get_teststatus +from ddtrace.contrib.pytest._atr_utils import atr_handle_retries +from ddtrace.contrib.pytest._atr_utils import atr_pytest_terminal_summary_post_yield from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled from ddtrace.contrib.pytest._retry_utils import get_retry_num @@ -28,6 +32,7 @@ from ddtrace.contrib.pytest._utils import _is_enabled_early from ddtrace.contrib.pytest._utils import _is_test_unskippable from ddtrace.contrib.pytest._utils import _pytest_marked_to_skip +from ddtrace.contrib.pytest._utils import _pytest_version_supports_atr from ddtrace.contrib.pytest._utils import _pytest_version_supports_efd from ddtrace.contrib.pytest._utils import _TestOutcome from ddtrace.contrib.pytest.constants import FRAMEWORK @@ -399,11 +404,11 @@ def _process_result(item, call, result) -> _TestOutcome: InternalTest.set_tag(test_id, test.RESULT, test.Status.XPASS.value) return _TestOutcome(TestStatus.FAIL) - # NOTE: for EFD purposes, we need to know if the test failed during setup or teardown. + # NOTE: for ATR and EFD purposes, we need to know if the test failed during setup or teardown. if call.when == "setup" and result.failed: - InternalTest.set_tag(test_id, "_dd.ci.efd_setup_failed", True) + InternalTest.stash_set(test_id, "setup_failed", True) elif call.when == "teardown" and result.failed: - InternalTest.set_tag(test_id, "_dd.ci.efd_teardown_failed", True) + InternalTest.stash_set(test_id, "teardown_failed", True) exc_info = TestExcInfo(call.excinfo.type, call.excinfo.value, call.excinfo.tb) if call.excinfo else None @@ -411,8 +416,7 @@ def _process_result(item, call, result) -> _TestOutcome: def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome: pytest_TestReport) -> None: - # When EFD retries are active, we do not want makereport to generate results, but we want to record exceptions - # since they are not available in the output of runtest_protocol + # When ATR or EFD retries are active, we do not want makereport to generate results if get_retry_num(item.nodeid) is not None: return @@ -431,10 +435,15 @@ def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome if not InternalTest.is_finished(test_id): InternalTest.finish(test_id, test_outcome.status, test_outcome.skip_reason, test_outcome.exc_info) - # EFD retries tests only if their teardown succeeded to ensure the best chance they will succeed + # ATR and EFD retry tests only if their teardown succeeded to ensure the best chance the retry will succeed # NOTE: this mutates the original result's outcome + if InternalTest.stash_get(test_id, "setup_failed") or InternalTest.stash_get(test_id, "teardown_failed"): + log.debug("Test %s failed during setup or teardown, skipping retries", test_id) + return if InternalTestSession.efd_enabled() and InternalTest.efd_should_retry(test_id): return efd_handle_retries(test_id, item, call.when, original_result, test_outcome) + if InternalTestSession.atr_is_enabled() and InternalTest.atr_should_retry(test_id): + return atr_handle_retries(test_id, item, call.when, original_result, test_outcome) @pytest.hookimpl(hookwrapper=True) @@ -452,9 +461,7 @@ def pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo) -> None: log.debug("encountered error during makereport", exc_info=True) -@pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_terminal_summary(terminalreporter, exitstatus, config): - """Report flaky or failed tests""" +def _pytest_terminal_summary_pre_yield(terminalreporter) -> int: # Before yield gives us a chance to show failure reports, but they have to be in terminalreporter.stats["failed"] to # be shown. That, however, would make them count towards the final summary, so we add them temporarily, then restore # terminalreporter.stats["failed"] to its original size after the yield. @@ -465,14 +472,23 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config): failed_report.outcome = PYTEST_STATUS.FAILED terminalreporter.stats.setdefault("failed", []).append(failed_report) - yield + if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): + for failed_report in atr_get_failed_reports(terminalreporter): + failed_report.outcome = PYTEST_STATUS.FAILED + terminalreporter.stats.setdefault("failed", []).append(failed_report) + return failed_reports_initial_size + + +def _pytest_terminal_summary_post_yield(terminalreporter, failed_reports_initial_size: t.Optional[int] = None): # After yield gives us a chance to: # - print our flaky test status summary # - modify the total counts # Restore terminalreporter.stats["failed"] to its original size so the final summary remains correct - if failed_reports_initial_size == 0: + if failed_reports_initial_size is None: + log.debug("Could not get initial failed report size, not restoring failed reports") + elif failed_reports_initial_size == 0: terminalreporter.stats.pop("failed", None) else: terminalreporter.stats[PYTEST_STATUS.FAILED] = terminalreporter.stats[PYTEST_STATUS.FAILED][ @@ -483,6 +499,27 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config): if _pytest_version_supports_efd() and InternalTestSession.efd_enabled(): efd_pytest_terminal_summary_post_yield(terminalreporter) + if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): + atr_pytest_terminal_summary_post_yield(terminalreporter) + return + + +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_terminal_summary(terminalreporter, exitstatus, config): + """Report flaky or failed tests""" + failed_reports_initial_size = None + try: + failed_reports_initial_size = _pytest_terminal_summary_pre_yield(terminalreporter) + except Exception: # noqa: E722 + log.debug("Encountered error during terminal summary pre-yield", exc_info=True) + + yield + + try: + _pytest_terminal_summary_post_yield(terminalreporter, failed_reports_initial_size) + except Exception: # noqa: E722 + log.debug("Encountered error during terminal summary post-yield", exc_info=True) + return @@ -490,7 +527,9 @@ def _pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: if not is_test_visibility_enabled(): return - if InternalTestSession.efd_has_failed_tests(): + if InternalTestSession.efd_enabled() and InternalTestSession.efd_has_failed_tests(): + session.exitstatus = pytest.ExitCode.TESTS_FAILED + if InternalTestSession.atr_has_failed_tests() and InternalTestSession.atr_has_failed_tests(): session.exitstatus = pytest.ExitCode.TESTS_FAILED invoked_by_coverage_run_status = _is_coverage_invoked_by_coverage_run() @@ -524,8 +563,15 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: def pytest_report_teststatus( report: pytest_TestReport, ) -> t.Optional[pytest_TestShortLogReport]: - if _pytest_version_supports_efd(): - return efd_get_teststatus(report) + if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): + test_status = atr_get_teststatus(report) + if test_status is not None: + return test_status + + if _pytest_version_supports_efd() and InternalTestSession.efd_enabled(): + test_status = efd_get_teststatus(report) + if test_status is not None: + return test_status @pytest.hookimpl(trylast=True) diff --git a/ddtrace/contrib/pytest/_retry_utils.py b/ddtrace/contrib/pytest/_retry_utils.py index aa1ea727cf8..4f76d07d68c 100644 --- a/ddtrace/contrib/pytest/_retry_utils.py +++ b/ddtrace/contrib/pytest/_retry_utils.py @@ -3,9 +3,14 @@ import typing as t import _pytest +from _pytest.logging import caplog_handler_key +from _pytest.logging import caplog_records_key from _pytest.runner import CallInfo import pytest +from ddtrace.contrib.pytest._utils import _TestOutcome +from ddtrace.ext.test_visibility.api import TestExcInfo +from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal import core @@ -30,11 +35,65 @@ def set_retry_num(nodeid: str, retry_num: int): yield -def _efd_get_attempt_string(nodeid) -> str: +def _get_retry_attempt_string(nodeid) -> str: retry_number = get_retry_num(nodeid) return "ATTEMPT {} ".format(retry_number) if retry_number is not None else "INITIAL ATTEMPT " +def _get_outcome_from_retry( + item: pytest.Item, + outcomes: RetryOutcomes, +) -> _TestOutcome: + _outcome_status: t.Optional[TestStatus] = None + _outcome_skip_reason: t.Optional[str] = None + _outcome_exc_info: t.Optional[TestExcInfo] = None + + # _initrequest() needs to be called first because the test has already executed once + item._initrequest() + + # Setup + setup_call, setup_report = _retry_run_when(item, "setup", outcomes) + if setup_report.outcome == outcomes.FAILED: + _outcome_status = TestStatus.FAIL + if setup_call.excinfo is not None: + _outcome_exc_info = TestExcInfo(setup_call.excinfo.type, setup_call.excinfo.value, setup_call.excinfo.tb) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + if setup_report.outcome == outcomes.SKIPPED: + _outcome_status = TestStatus.SKIP + + # Call + if setup_report.outcome == outcomes.PASSED: + call_call, call_report = _retry_run_when(item, "call", outcomes) + if call_report.outcome == outcomes.FAILED: + _outcome_status = TestStatus.FAIL + if call_call.excinfo is not None: + _outcome_exc_info = TestExcInfo(call_call.excinfo.type, call_call.excinfo.value, call_call.excinfo.tb) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + elif call_report.outcome == outcomes.SKIPPED: + _outcome_status = TestStatus.SKIP + elif call_report.outcome == outcomes.PASSED: + _outcome_status = TestStatus.PASS + + # Teardown does not happen if setup skipped + if not setup_report.skipped: + teardown_call, teardown_report = _retry_run_when(item, "teardown", outcomes) + # Only override the outcome if the teardown failed, otherwise defer to either setup or call outcome + if teardown_report.outcome == outcomes.FAILED: + _outcome_status = TestStatus.FAIL + if teardown_call.excinfo is not None: + _outcome_exc_info = TestExcInfo( + teardown_call.excinfo.type, teardown_call.excinfo.value, teardown_call.excinfo.tb + ) + item.stash[caplog_records_key] = {} + item.stash[caplog_handler_key] = {} + + item._initrequest() + + return _TestOutcome(status=_outcome_status, skip_reason=_outcome_skip_reason, exc_info=_outcome_exc_info) + + def _retry_run_when(item, when, outcomes: RetryOutcomes) -> t.Tuple[CallInfo, _pytest.reports.TestReport]: hooks = { "setup": item.ihook.pytest_runtest_setup, diff --git a/ddtrace/contrib/pytest/_utils.py b/ddtrace/contrib/pytest/_utils.py index ab05e14caad..500fbc8d8c6 100644 --- a/ddtrace/contrib/pytest/_utils.py +++ b/ddtrace/contrib/pytest/_utils.py @@ -7,6 +7,7 @@ import pytest +from ddtrace.contrib.pytest.constants import ATR_MIN_SUPPORTED_VERSION from ddtrace.contrib.pytest.constants import EFD_MIN_SUPPORTED_VERSION from ddtrace.contrib.pytest.constants import ITR_MIN_SUPPORTED_VERSION from ddtrace.contrib.pytest.constants import RETRIES_MIN_SUPPORTED_VERSION @@ -169,6 +170,10 @@ def _pytest_version_supports_efd(): return _get_pytest_version_tuple() >= EFD_MIN_SUPPORTED_VERSION +def _pytest_version_supports_atr(): + return _get_pytest_version_tuple() >= ATR_MIN_SUPPORTED_VERSION + + def _pytest_marked_to_skip(item: pytest.Item) -> bool: """Checks whether Pytest will skip an item""" if item.get_closest_marker("skip") is not None: diff --git a/ddtrace/contrib/pytest/constants.py b/ddtrace/contrib/pytest/constants.py index ccb76c06d23..cc5d768fc38 100644 --- a/ddtrace/contrib/pytest/constants.py +++ b/ddtrace/contrib/pytest/constants.py @@ -8,3 +8,4 @@ ITR_MIN_SUPPORTED_VERSION = (7, 2, 0) RETRIES_MIN_SUPPORTED_VERSION = (7, 0, 0) EFD_MIN_SUPPORTED_VERSION = RETRIES_MIN_SUPPORTED_VERSION +ATR_MIN_SUPPORTED_VERSION = RETRIES_MIN_SUPPORTED_VERSION diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index a9bb68e70be..fd27c7b6a9f 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -139,6 +139,8 @@ def __init__( self._span: Optional[Span] = None self._tags: Dict[str, Any] = initial_tags if initial_tags else {} + self._stash: Dict[str, Any] = {} + # ITR-related attributes self._is_itr_skipped: bool = False self._itr_skipped_count: int = 0 @@ -199,6 +201,9 @@ def _finish_span(self, override_finish_time: Optional[float] = None) -> None: if self._session_settings.efd_settings is not None and self._session_settings.efd_settings.enabled: self._set_efd_tags() + if self._session_settings.atr_settings is not None and self._session_settings.atr_settings.enabled: + self._set_atr_tags() + # Allow item-level _set_span_tags() to potentially overwrite default and hierarchy tags. self._set_span_tags() @@ -257,6 +262,10 @@ def _set_efd_tags(self) -> None: """EFD tags are only set at the test or session level""" pass + def _set_atr_tags(self) -> None: + """ATR tags are only set at the test level""" + pass + def _set_span_tags(self): """This is effectively a callback method for exceptional cases where the item span needs to be modified directly by the class @@ -475,6 +484,15 @@ def get_coverage_data(self) -> Optional[Dict[Path, CoverageLines]]: return None return self._coverage_data.get_data() + def stash_set(self, key: str, value: object) -> None: + self._stash[key] = value + + def stash_get(self, key: str) -> object: + return self._stash.get(key) + + def stash_delete(self, key: str) -> object: + return self._stash.pop(key, None) + class TestVisibilityChildItem(TestVisibilityItemBase, Generic[CIDT]): pass diff --git a/ddtrace/internal/ci_visibility/api/_session.py b/ddtrace/internal/ci_visibility/api/_session.py index 61e14c5a552..b6407cc86be 100644 --- a/ddtrace/internal/ci_visibility/api/_session.py +++ b/ddtrace/internal/ci_visibility/api/_session.py @@ -5,6 +5,7 @@ from ddtrace.ext import test from ddtrace.ext.test_visibility import ITR_SKIPPING_LEVEL from ddtrace.ext.test_visibility._item_ids import TestModuleId +from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal.ci_visibility.api._base import TestVisibilityParentItem from ddtrace.internal.ci_visibility.api._base import TestVisibilitySessionSettings from ddtrace.internal.ci_visibility.api._module import TestVisibilityModule @@ -166,3 +167,14 @@ def atr_max_retries_reached(self) -> bool: def _atr_count_retry(self): self._atr_total_retries += 1 + + def atr_has_failed_tests(self): + if not self._session_settings.atr_settings.enabled: + return False + + for _module in self._children.values(): + for _suite in _module._children.values(): + for _test in _suite._children.values(): + if _test.atr_has_retries() and _test.atr_get_final_status() == TestStatus.FAIL: + return True + return False diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index 83828d4b764..a0d7a6e0fa9 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -104,6 +104,10 @@ def _set_efd_tags(self) -> None: if self._is_new: self.set_tag(TEST_IS_NEW, self._is_new) + def _set_atr_tags(self) -> None: + if self._atr_is_retry: + self.set_tag(TEST_IS_RETRY, self._atr_is_retry) + def _set_span_tags(self) -> None: """This handles setting tags that can't be properly stored in self._tags @@ -127,6 +131,7 @@ def _telemetry_record_event_finished(self): test_framework=self._session_settings.test_framework_metric_name, is_benchmark=self._is_benchmark if self._is_benchmark is not None else None, is_new=self._is_new if self._is_new is not None else None, + is_retry=self._efd_is_retry or self._atr_is_retry, early_flake_detection_abort_reason=self._efd_abort_reason, ) @@ -164,6 +169,8 @@ def get_status(self) -> Union[TestStatus, SPECIAL_STATUS]: if efd_status == EFDTestStatus.ALL_SKIP: return TestStatus.SKIP return TestStatus.FAIL + if self.atr_has_retries(): + return self.atr_get_final_status() return super().get_status() def count_itr_skipped(self) -> None: @@ -322,6 +329,9 @@ def _atr_make_retry_test(self): return retry_test + def atr_has_retries(self) -> bool: + return len(self._atr_retries) > 0 + def atr_should_retry(self): if not self._session_settings.atr_settings.enabled: return False diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 4f4d5e57804..42d37c34927 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -1172,10 +1172,31 @@ def _on_item_is_finished(item_id: TestVisibilityItemId) -> bool: return CIVisibility.get_item_by_id(item_id).is_finished() +@_requires_civisibility_enabled +def _on_item_stash_set(item_id: TestVisibilityItemId, key: str, value: object) -> None: + log.debug("Handling stash set for item %s, key %s, value %s", item_id, key, value) + CIVisibility.get_item_by_id(item_id).stash_set(key, value) + + +@_requires_civisibility_enabled +def _on_item_stash_get(item_id: TestVisibilityItemId, key: str) -> Optional[object]: + log.debug("Handling stash get for item %s, key %s", item_id, key) + return CIVisibility.get_item_by_id(item_id).stash_get(key) + + +@_requires_civisibility_enabled +def _on_item_stash_delete(item_id: TestVisibilityItemId, key: str) -> None: + log.debug("Handling stash delete for item %s, key %s", item_id, key) + CIVisibility.get_item_by_id(item_id).stash_delete(key) + + def _register_item_handlers(): log.debug("Registering item handlers") core.on("test_visibility.item.get_span", _on_item_get_span, "span") core.on("test_visibility.item.is_finished", _on_item_is_finished, "is_finished") + core.on("test_visibility.item.stash_set", _on_item_stash_set) + core.on("test_visibility.item.stash_get", _on_item_stash_get, "stash_value") + core.on("test_visibility.item.stash_delete", _on_item_stash_delete) @_requires_civisibility_enabled @@ -1388,6 +1409,11 @@ def _on_atr_is_enabled() -> bool: return CIVisibility.is_atr_enabled() +@_requires_civisibility_enabled +def _on_atr_session_has_failed_tests() -> bool: + return CIVisibility.get_session().atr_has_failed_tests() + + @_requires_civisibility_enabled def _on_atr_should_retry_test(item_id: InternalTestId) -> bool: return CIVisibility.get_test_by_id(item_id).atr_should_retry() @@ -1418,6 +1444,7 @@ def _on_atr_get_final_status(test_id: InternalTestId) -> TestStatus: def _register_atr_handlers(): log.debug("Registering ATR handlers") core.on("test_visibility.atr.is_enabled", _on_atr_is_enabled, "is_enabled") + core.on("test_visibility.atr.session_has_failed_tests", _on_atr_session_has_failed_tests, "has_failed_tests") core.on("test_visibility.atr.should_retry_test", _on_atr_should_retry_test, "should_retry_test") core.on("test_visibility.atr.add_retry", _on_atr_add_retry, "retry_number") core.on("test_visibility.atr.start_retry", _on_atr_start_retry) diff --git a/ddtrace/internal/ci_visibility/telemetry/events.py b/ddtrace/internal/ci_visibility/telemetry/events.py index bb19fb627f6..fb586a4cc96 100644 --- a/ddtrace/internal/ci_visibility/telemetry/events.py +++ b/ddtrace/internal/ci_visibility/telemetry/events.py @@ -28,6 +28,7 @@ def _record_event( is_unsupported_ci: Optional[bool] = False, is_benchmark: Optional[bool] = False, is_new: Optional[bool] = False, + is_retry: Optional[bool] = False, early_flake_detection_abort_reason: Optional[str] = None, ): if has_codeowners and event_type != EVENT_TYPES.SESSION: @@ -40,6 +41,10 @@ def _record_event( log.debug( "is_new tag can only be set for test finishes, but event type is %s and event is %s", event_type, event ) + if is_retry and not (event_type == EVENT_TYPES.TEST and event == EVENTS_TELEMETRY.FINISHED): + log.debug( + "is_retry tag can only be set for test finishes, but event type is %s and event is %s", event_type, event + ) if early_flake_detection_abort_reason and ( event_type not in [EVENT_TYPES.TEST, EVENT_TYPES.SESSION] or event != EVENTS_TELEMETRY.FINISHED ): @@ -56,8 +61,11 @@ def _record_event( if event_type == EVENT_TYPES.TEST: _tags.append(("is_benchmark", "1" if is_benchmark else "0")) - if event == EVENTS_TELEMETRY.FINISHED and is_new: - _tags.append(("is_new", "1")) + if event == EVENTS_TELEMETRY.FINISHED: + if is_new: + _tags.append(("is_new", "true")) + if is_retry: + _tags.append(("is_retry", "true")) if ( early_flake_detection_abort_reason @@ -97,6 +105,7 @@ def record_event_finished( is_unsupported_ci: bool = False, is_benchmark: bool = False, is_new: bool = False, + is_retry: bool = False, early_flake_detection_abort_reason: Optional[str] = None, ): _record_event( @@ -107,6 +116,7 @@ def record_event_finished( is_unsupported_ci=is_unsupported_ci, is_benchmark=is_benchmark, is_new=is_new, + is_retry=is_retry, early_flake_detection_abort_reason=early_flake_detection_abort_reason, ) diff --git a/ddtrace/internal/test_visibility/_atr_mixins.py b/ddtrace/internal/test_visibility/_atr_mixins.py index c4f86131eb9..71737bf09e3 100644 --- a/ddtrace/internal/test_visibility/_atr_mixins.py +++ b/ddtrace/internal/test_visibility/_atr_mixins.py @@ -27,6 +27,16 @@ def atr_is_enabled() -> bool: log.debug("Auto Test Retries enabled: %s", is_enabled) return is_enabled + @staticmethod + @_catch_and_log_exceptions + def atr_has_failed_tests() -> bool: + log.debug("Checking if session has failed tests for Auto Test Retries") + has_failed_tests = core.dispatch_with_results( + "test_visibility.atr.session_has_failed_tests" + ).has_failed_tests.value + log.debug("Session has ATR failed tests: %s", has_failed_tests) + return has_failed_tests + class ATRTestMixin: @staticmethod diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index 6f56f290321..c5084d320cb 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -30,6 +30,26 @@ class InternalTestBase(ext_api.TestBase): def get_span(item_id: t.Union[ext_api.TestVisibilityItemId, InternalTestId]) -> Span: return _get_item_span(item_id) + @staticmethod + @_catch_and_log_exceptions + def stash_set(item_id, key: str, value: object): + log.debug("Stashing value %s for key %s in item %s", value, key, item_id) + core.dispatch("test_visibility.item.stash_set", (item_id, key, value)) + + @staticmethod + @_catch_and_log_exceptions + def stash_get(item_id: ext_api.TestVisibilityItemId, key: str): + log.debug("Getting stashed value for key %s in item %s", key, item_id) + stash_value = core.dispatch_with_results("test_visibility.item.stash_get", (item_id, key)).stash_value.value + log.debug("Got stashed value %s for key %s in item %s", stash_value, key, item_id) + return stash_value + + @staticmethod + @_catch_and_log_exceptions + def stash_delete(item_id: ext_api.TestVisibilityItemId, key: str): + log.debug("Deleting stashed value for key %s in item %s", key, item_id) + core.dispatch("test_visibility.item.stash_delete", (item_id, key)) + class InternalTestSession(ext_api.TestSession, EFDSessionMixin, ATRSessionMixin): @staticmethod diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index 796dd7422e2..eeefa59f714 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -97,7 +97,7 @@ def _dummy_check_enabled_features(self): ): yield - def inline_run(self, *args, mock_ci_env=True, block_gitlab_env=False, project_dir=None): + def inline_run(self, *args, mock_ci_env=True, block_gitlab_env=False, project_dir=None, extra_env=None): """Execute test script with test tracer.""" class CIVisibilityPlugin: @@ -125,6 +125,9 @@ def pytest_unconfigure(config): if block_gitlab_env: _test_env["GITLAB_CI"] = "0" + if extra_env: + _test_env.update(extra_env) + with _ci_override_env(_test_env, replace_os_env=True): return self.testdir.inline_run("-p", "no:randomly", *args, plugins=[CIVisibilityPlugin()]) diff --git a/tests/contrib/pytest/test_pytest_atr.py b/tests/contrib/pytest/test_pytest_atr.py new file mode 100644 index 00000000000..742a4f220d0 --- /dev/null +++ b/tests/contrib/pytest/test_pytest_atr.py @@ -0,0 +1,244 @@ +"""Tests Early Flake Detection (EFD) functionality + +The tests in this module only validate the behavior of EFD, so only counts and statuses of tests, retries, and sessions +are checked. + +- The same known tests are used to override fetching of known tests. +- The session object is patched to never be a faulty session, by default. +""" +from unittest import mock + +import pytest + +from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 +from ddtrace.contrib.pytest._utils import _pytest_version_supports_atr +from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings +from tests.ci_visibility.util import _get_default_civisibility_ddconfig +from tests.contrib.pytest.test_pytest import PytestTestCaseBase +from tests.contrib.pytest.test_pytest import _get_spans_from_list + + +pytestmark = pytest.mark.skipif( + not (_USE_PLUGIN_V2 and _pytest_version_supports_atr()), + reason="Auto Test Retries requires v2 of the plugin and pytest >=7.0", +) + +_TEST_PASS_CONTENT = """ +def test_func_pass(): + assert True +""" + +_TEST_FAIL_CONTENT = """ +import pytest + +def test_func_fail(): + assert False + +_test_func_retries_skip_count = 0 +def test_func_retries_skip(): + global _test_func_retries_skip_count + _test_func_retries_skip_count += 1 + if _test_func_retries_skip_count > 1: + pytest.skip() + assert False + +""" + +_TEST_PASS_ON_RETRIES_CONTENT = """ +_test_func_passes_4th_retry_count = 0 +def test_func_passes_4th_retry(): + global _test_func_passes_4th_retry_count + _test_func_passes_4th_retry_count += 1 + assert _test_func_passes_4th_retry_count == 5 + +_test_func_passes_1st_retry_count = 0 +def test_func_passes_1st_retry(): + global _test_func_passes_1st_retry_count + _test_func_passes_1st_retry_count += 1 + assert _test_func_passes_1st_retry_count == 2 +""" + +_TEST_ERRORS_CONTENT = """ +import pytest + +@pytest.fixture +def fixture_fails_setup(): + assert False + +def test_func_fails_setup(fixture_fails_setup): + assert True + +@pytest.fixture +def fixture_fails_teardown(): + yield + assert False + +def test_func_fails_teardown(fixture_fails_teardown): + assert True +""" + +_TEST_SKIP_CONTENT = """ +import pytest + +@pytest.mark.skip +def test_func_skip_mark(): + assert True + +def test_func_skip_inside(): + pytest.skip() +""" + + +class PytestATRTestCase(PytestTestCaseBase): + @pytest.fixture(autouse=True, scope="function") + def set_up_atr(self): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings(flaky_test_retries_enabled=True), + ): + yield + from ddtrace.internal.ci_visibility.recorder import CIVisibility + + if CIVisibility.enabled: + CIVisibility.disable() + + def test_pytest_atr_no_ddtrace_does_not_retry(self): + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_fail=_TEST_FAIL_CONTENT) + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + rec = self.inline_run() + rec.assertoutcome(passed=2, failed=6, skipped=2) + assert len(self.pop_spans()) == 0 + + def test_pytest_atr_env_var_disables_retrying(self): + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_fail=_TEST_FAIL_CONTENT) + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + + with mock.patch("ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig()): + rec = self.inline_run("--ddtrace", "-s", extra_env={"DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": "0"}) + rec.assertoutcome(passed=2, failed=6, skipped=2) + assert len(self.pop_spans()) > 0 + + def test_pytest_atr_env_var_does_not_override_api(self): + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_fail=_TEST_FAIL_CONTENT) + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig() + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings(flaky_test_retries_enabled=False), + ): + rec = self.inline_run("--ddtrace", extra_env={"DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": "1"}) + rec.assertoutcome(passed=2, failed=6, skipped=2) + assert len(self.pop_spans()) > 0 + + def test_pytest_atr_spans(self): + """Tests that an EFD session properly does the correct number of retries and sets the correct tags""" + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_fail=_TEST_FAIL_CONTENT) + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + + rec = self.inline_run("--ddtrace", "-v") + assert rec.ret == 1 + spans = self.pop_spans() + session_span = _get_spans_from_list(spans, "session")[0] + assert session_span.get_tag("test.status") == "fail" + + module_spans = _get_spans_from_list(spans, "module") + assert len(module_spans) == 1 + assert module_spans[0].get_tag("test.status") == "fail" + + suite_spans = _get_spans_from_list(spans, "suite") + assert len(suite_spans) == 5 + for suite_span in suite_spans: + suite_name = suite_span.get_tag("test.suite") + if suite_name in ("test_errors.py", "test_fail.py"): + assert suite_span.get_tag("test.status") == "fail" + elif suite_name == "test_skip.py": + assert suite_span.get_tag("test.status") == "skip" + else: + assert suite_span.get_tag("test.status") == "pass" + + func_fail_spans = _get_spans_from_list(spans, "test", "test_func_fail") + assert len(func_fail_spans) == 6 + func_fail_retries = 0 + for func_fail_span in func_fail_spans: + assert func_fail_span.get_tag("test.status") == "fail" + if func_fail_span.get_tag("test.is_retry") == "true": + func_fail_retries += 1 + assert func_fail_retries == 5 + + func_fail_skip_spans = _get_spans_from_list(spans, "test", "test_func_retries_skip") + assert len(func_fail_skip_spans) == 6 + func_fail_skip_retries = 0 + for func_fail_skip_span in func_fail_skip_spans: + func_fail_skip_is_retry = func_fail_skip_span.get_tag("test.is_retry") == "true" + assert func_fail_skip_span.get_tag("test.status") == ("skip" if func_fail_skip_is_retry else "fail") + if func_fail_skip_is_retry: + func_fail_skip_retries += 1 + assert func_fail_skip_retries == 5 + + func_pass_spans = _get_spans_from_list(spans, "test", "test_func_pass") + assert len(func_pass_spans) == 1 + assert func_pass_spans[0].get_tag("test.status") == "pass" + assert func_pass_spans[0].get_tag("test.retry") is None + + # Skips are tested twice: once with a skip mark (skips during setup) and once using pytest.skip() in the + # test body (skips during call), neither should be retried + func_skip_mark_spans = _get_spans_from_list(spans, "test", "test_func_skip_mark") + assert len(func_skip_mark_spans) == 1 + assert func_skip_mark_spans[0].get_tag("test.status") == "skip" + assert func_skip_mark_spans[0].get_tag("test.is_retry") is None + + func_skip_inside_spans = _get_spans_from_list(spans, "test", "test_func_skip_inside") + assert len(func_skip_inside_spans) == 1 + assert func_skip_inside_spans[0].get_tag("test.status") == "skip" + assert func_skip_inside_spans[0].get_tag("test.is_retry") is None + + assert len(spans) == 31 + + def test_pytest_atr_fails_session_when_test_fails(self): + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_fail=_TEST_FAIL_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + assert rec.ret == 1 + assert len(spans) == 28 + + def test_pytest_atr_passes_session_when_test_pass(self): + self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) + self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) + self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) + + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + assert rec.ret == 0 + assert len(spans) == 15 + + def test_pytest_atr_does_not_retry_failed_setup_or_teardown(self): + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) + rec = self.inline_run("--ddtrace") + spans = self.pop_spans() + fails_setup_spans = _get_spans_from_list(spans, "test", "test_func_fails_setup") + assert len(fails_setup_spans) == 1 + assert fails_setup_spans[0].get_tag("test.is_retry") != "true" + + fails_teardown_spans = _get_spans_from_list(spans, "test", "test_func_fails_teardown") + assert len(fails_teardown_spans) == 1 + assert fails_teardown_spans[0].get_tag("test.is_retry") != "true" + + assert rec.ret == 1 + assert len(spans) == 5 diff --git a/tests/contrib/pytest/test_pytest_efd.py b/tests/contrib/pytest/test_pytest_efd.py index b034b7ad892..f08e170d089 100644 --- a/tests/contrib/pytest/test_pytest_efd.py +++ b/tests/contrib/pytest/test_pytest_efd.py @@ -121,7 +121,7 @@ def set_up_efd(self): if CIVisibility.enabled: CIVisibility.disable() - def test_pytest_no_ddtrace_does_not_retry(self): + def test_pytest_efd_no_ddtrace_does_not_retry(self): self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) self.testdir.makepyfile(test_known_fail=_TEST_KNOWN_FAIL_CONTENT) self.testdir.makepyfile(test_new_pass=_TEST_NEW_PASS_CONTENT) diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json index d2563710d41..fc182f0ce3c 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -52,9 +52,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -62,12 +62,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 70791, - "start": 1730218084512439542 + "duration": 77000, + "start": 1730382044807671634 }], [ { @@ -84,7 +84,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -95,7 +95,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -106,26 +106,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -133,12 +134,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 71417, - "start": 1730218084528357708 + "duration": 109958, + "start": 1730382044822965176 }], [ { @@ -155,7 +156,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -166,7 +167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -177,26 +178,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -204,12 +206,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 50042, - "start": 1730218084528531125 + "duration": 68542, + "start": 1730382044823201342 }], [ { @@ -226,7 +228,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -237,7 +239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -248,26 +250,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -275,12 +278,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46041, - "start": 1730218084528663917 + "duration": 85208, + "start": 1730382044823359676 }], [ { @@ -297,7 +300,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -308,7 +311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -319,26 +322,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -346,12 +350,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43709, - "start": 1730218084528787458 + "duration": 57750, + "start": 1730382044823556884 }], [ { @@ -368,7 +372,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -379,7 +383,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -390,26 +394,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -417,12 +422,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 41209, - "start": 1730218084528907333 + "duration": 56375, + "start": 1730382044823719217 }], [ { @@ -439,7 +444,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -450,7 +455,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -461,26 +466,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -488,12 +494,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 51625, - "start": 1730218084529022417 + "duration": 47375, + "start": 1730382044823861717 }], [ { @@ -510,7 +516,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -521,7 +527,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -532,26 +538,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -559,12 +566,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43667, - "start": 1730218084529172500 + "duration": 48041, + "start": 1730382044823994051 }], [ { @@ -581,7 +588,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -592,7 +599,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -603,11 +610,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -619,9 +626,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -629,10 +636,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 32542, - "start": 1730218084529293625 + "duration": 34500, + "start": 1730382044824125717 }], [ { @@ -649,7 +656,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -660,7 +667,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -671,25 +678,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -697,10 +705,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30833, - "start": 1730218084529397500 + "duration": 35000, + "start": 1730382044824240176 }], [ { @@ -717,7 +725,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -728,7 +736,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -739,25 +747,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -765,10 +774,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30958, - "start": 1730218084529498625 + "duration": 33916, + "start": 1730382044824352301 }], [ { @@ -785,7 +794,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -796,7 +805,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -807,25 +816,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -833,10 +843,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 28875, - "start": 1730218084529601625 + "duration": 108042, + "start": 1730382044824463217 }], [ { @@ -853,7 +863,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -864,7 +874,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -875,25 +885,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -901,10 +912,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29208, - "start": 1730218084529698875 + "duration": 44583, + "start": 1730382044824735634 }], [ { @@ -921,7 +932,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -932,7 +943,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -943,25 +954,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -969,10 +981,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30542, - "start": 1730218084529799500 + "duration": 37291, + "start": 1730382044824865551 }], [ { @@ -989,7 +1001,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1000,7 +1012,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1011,25 +1023,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1037,10 +1050,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 28959, - "start": 1730218084529901083 + "duration": 35083, + "start": 1730382044824988551 }], [ { @@ -1057,7 +1070,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1068,7 +1081,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1079,25 +1092,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1105,10 +1119,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29959, - "start": 1730218084530000583 + "duration": 34459, + "start": 1730382044825100842 }], [ { @@ -1125,7 +1139,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1136,7 +1150,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1147,11 +1161,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1165,9 +1179,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1175,12 +1189,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 12, "test.source.start": 4 }, - "duration": 56334, - "start": 1730218084530112708 + "duration": 66167, + "start": 1730382044825214592 }], [ { @@ -1197,7 +1211,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1208,7 +1222,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1219,11 +1233,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1236,9 +1250,9 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1246,10 +1260,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 31416, - "start": 1730218084530239167 + "duration": 33834, + "start": 1730382044825351842 }], [ { @@ -1266,7 +1280,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1277,7 +1291,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1288,11 +1302,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1305,9 +1319,9 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1315,10 +1329,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30417, - "start": 1730218084530336958 + "duration": 31125, + "start": 1730382044825450842 }], [ { @@ -1335,7 +1349,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1346,7 +1360,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1357,26 +1371,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1384,10 +1399,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29875, - "start": 1730218084530437917 + "duration": 32708, + "start": 1730382044825556426 }], [ { @@ -1404,7 +1419,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1415,7 +1430,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1426,26 +1441,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1453,10 +1469,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 31291, - "start": 1730218084530538417 + "duration": 47250, + "start": 1730382044825664134 }], [ { @@ -1473,7 +1489,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1484,7 +1500,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1495,26 +1511,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1522,10 +1539,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 28958, - "start": 1730218084530639792 + "duration": 31791, + "start": 1730382044825785926 }], [ { @@ -1542,7 +1559,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1553,7 +1570,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1564,26 +1581,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1591,10 +1609,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 33375, - "start": 1730218084530757958 + "duration": 31000, + "start": 1730382044825889467 }], [ { @@ -1611,7 +1629,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1622,7 +1640,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1633,26 +1651,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1660,10 +1679,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 31916, - "start": 1730218084530872292 + "duration": 31625, + "start": 1730382044825991676 }], [ { @@ -1680,7 +1699,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1691,7 +1710,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1702,11 +1721,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1719,9 +1738,9 @@ "test.parameters": "{\"param1\": \"value3\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test" }, "metrics": { @@ -1729,10 +1748,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 32250, - "start": 1730218084530980958 + "duration": 31458, + "start": 1730382044826095759 }], [ { @@ -1749,7 +1768,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1760,7 +1779,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1771,11 +1790,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1785,7 +1804,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "11513418841639438065", + "test_session_id": "14120222913796326459", "type": "test_session_end" }, "metrics": { @@ -1793,10 +1812,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 21548709, - "start": 1730218084511516958 + "duration": 20716333, + "start": 1730382044807300259 }, { "name": "test_visibility.module", @@ -1812,7 +1831,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1823,7 +1842,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1834,7 +1853,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1849,8 +1868,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "fail", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", "type": "test_module_end" }, "metrics": { @@ -1858,8 +1877,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18848917, - "start": 1730218084512381083 + "duration": 18690542, + "start": 1730382044807618967 }, { "name": "test_visibility.suite", @@ -1875,7 +1894,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1886,7 +1905,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1897,7 +1916,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1911,9 +1930,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "14522272526427506506", - "test_session_id": "11513418841639438065", - "test_suite_id": "382174887352048625", + "test_module_id": "13752089970478418748", + "test_session_id": "14120222913796326459", + "test_suite_id": "10844366306027865327", "type": "test_suite_end" }, "metrics": { @@ -1921,8 +1940,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18712917, - "start": 1730218084512410958 + "duration": 18582042, + "start": 1730382044807643342 }, { "name": "test_visibility.module", @@ -1938,7 +1957,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1949,7 +1968,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1960,7 +1979,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1975,8 +1994,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "fail", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", "type": "test_module_end" }, "metrics": { @@ -1984,8 +2003,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1673209, - "start": 1730218084531287958 + "duration": 1523500, + "start": 1730382044826401759 }, { "name": "test_visibility.suite", @@ -2001,7 +2020,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2012,7 +2031,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2023,7 +2042,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -2037,9 +2056,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test_suite_end" }, "metrics": { @@ -2047,8 +2066,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 979917, - "start": 1730218084531311875 + "duration": 890458, + "start": 1730382044826423509 }, { "name": "test_visibility.suite", @@ -2064,7 +2083,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2075,7 +2094,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2086,7 +2105,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -2100,9 +2119,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "13125642143907159606", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "12536544582847089510", "type": "test_suite_end" }, "metrics": { @@ -2110,8 +2129,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 509083, - "start": 1730218084532359542 + "duration": 486667, + "start": 1730382044827363842 }], [ { @@ -2128,7 +2147,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2139,7 +2158,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2150,11 +2169,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2166,9 +2185,9 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2176,10 +2195,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 34833, - "start": 1730218084531333292 + "duration": 31125, + "start": 1730382044826444009 }], [ { @@ -2196,7 +2215,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2207,7 +2226,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2218,11 +2237,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2234,9 +2253,9 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2244,10 +2263,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29584, - "start": 1730218084531438458 + "duration": 30125, + "start": 1730382044826544509 }], [ { @@ -2264,7 +2283,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2275,7 +2294,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2286,11 +2305,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2302,9 +2321,9 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2312,10 +2331,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 31041, - "start": 1730218084531534792 + "duration": 29917, + "start": 1730382044826639384 }], [ { @@ -2332,7 +2351,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2343,7 +2362,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2354,11 +2373,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2370,9 +2389,9 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2380,10 +2399,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29750, - "start": 1730218084531634958 + "duration": 29250, + "start": 1730382044826741342 }], [ { @@ -2400,7 +2419,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2411,7 +2430,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2422,11 +2441,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2438,9 +2457,9 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2448,10 +2467,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 35250, - "start": 1730218084531749583 + "duration": 27875, + "start": 1730382044826835134 }], [ { @@ -2468,7 +2487,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2479,7 +2498,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2490,11 +2509,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2506,9 +2525,9 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2516,10 +2535,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30291, - "start": 1730218084531855292 + "duration": 28459, + "start": 1730382044826925842 }], [ { @@ -2536,7 +2555,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2547,7 +2566,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2558,11 +2577,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2574,9 +2593,9 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2584,10 +2603,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 28708, - "start": 1730218084531950292 + "duration": 28458, + "start": 1730382044827018176 }], [ { @@ -2604,7 +2623,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2615,7 +2634,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2626,11 +2645,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2642,9 +2661,9 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2652,10 +2671,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29959, - "start": 1730218084532053458 + "duration": 28084, + "start": 1730382044827109592 }], [ { @@ -2672,7 +2691,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2683,7 +2702,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2694,11 +2713,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2710,9 +2729,9 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "9902770138436951004", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "13004843146614843617", "type": "test" }, "metrics": { @@ -2720,10 +2739,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 30625, - "start": 1730218084532149917 + "duration": 27500, + "start": 1730382044827201801 }], [ { @@ -2740,7 +2759,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2751,7 +2770,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2762,11 +2781,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2779,9 +2798,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "13125642143907159606", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "12536544582847089510", "type": "test" }, "metrics": { @@ -2789,12 +2808,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 2, "test.source.start": 1 }, - "duration": 49208, - "start": 1730218084532382792 + "duration": 43833, + "start": 1730382044827384634 }], [ { @@ -2811,7 +2830,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2822,7 +2841,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2833,11 +2852,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2849,9 +2868,9 @@ "test.name": "m2_s2_t2", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "13125642143907159606", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "12536544582847089510", "type": "test" }, "metrics": { @@ -2859,10 +2878,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 29916, - "start": 1730218084532503917 + "duration": 30042, + "start": 1730382044827497717 }], [ { @@ -2879,7 +2898,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2890,7 +2909,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2901,25 +2920,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s2_t2", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "13125642143907159606", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "12536544582847089510", "type": "test" }, "metrics": { @@ -2927,10 +2947,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585 + "process_id": 80706 }, - "duration": 32125, - "start": 1730218084532608375 + "duration": 41416, + "start": 1730382044827600426 }], [ { @@ -2947,7 +2967,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721086400000000", + "_dd.p.tid": "672388dc00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2958,7 +2978,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2969,11 +2989,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "547d4c5a3c14472aad35e4de84bf23f6", + "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -2987,9 +3007,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "1285008711190162976", - "test_session_id": "11513418841639438065", - "test_suite_id": "13125642143907159606", + "test_module_id": "12283267209251700888", + "test_session_id": "14120222913796326459", + "test_suite_id": "12536544582847089510", "type": "test" }, "metrics": { @@ -2997,10 +3017,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45585, + "process_id": 80706, "test.source.end": 12, "test.source.start": 4 }, - "duration": 49833, - "start": 1730218084532717292 + "duration": 46333, + "start": 1730382044827721176 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json index 795e6c41e58..2e6be6d87a0 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -52,9 +52,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -62,12 +62,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 73083, - "start": 1730218099485156341 + "duration": 76625, + "start": 1730382048418891386 }], [ { @@ -84,7 +84,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -95,7 +95,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -106,26 +106,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -133,12 +134,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 75709, - "start": 1730218099500880257 + "duration": 107625, + "start": 1730382048438090552 }], [ { @@ -155,7 +156,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -166,7 +167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -177,26 +178,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -204,12 +206,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52333, - "start": 1730218099501069924 + "duration": 59000, + "start": 1730382048438325302 }], [ { @@ -226,7 +228,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -237,7 +239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -248,26 +250,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -275,12 +278,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44208, - "start": 1730218099501203466 + "duration": 49000, + "start": 1730382048438472802 }], [ { @@ -297,7 +300,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -308,7 +311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -319,26 +322,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -346,12 +350,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 41583, - "start": 1730218099501321591 + "duration": 47958, + "start": 1730382048438603511 }], [ { @@ -368,7 +372,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -379,7 +383,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -390,11 +394,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -406,9 +410,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -416,10 +420,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 50291, - "start": 1730218099501440216 + "duration": 39708, + "start": 1730382048438757594 }], [ { @@ -436,7 +440,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -447,7 +451,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -458,25 +462,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -484,10 +489,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 94375, - "start": 1730218099501569216 + "duration": 35834, + "start": 1730382048438876177 }], [ { @@ -504,7 +509,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -515,7 +520,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -526,25 +531,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -552,10 +558,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 31417, - "start": 1730218099501739382 + "duration": 36334, + "start": 1730382048438990927 }], [ { @@ -572,7 +578,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -583,7 +589,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -594,11 +600,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -612,9 +618,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -622,12 +628,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 12, "test.source.start": 4 }, - "duration": 88000, - "start": 1730218099501842007 + "duration": 61250, + "start": 1730382048439103427 }], [ { @@ -644,7 +650,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -655,7 +661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -666,11 +672,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -683,9 +689,9 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -693,10 +699,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 45959, - "start": 1730218099502014257 + "duration": 34750, + "start": 1730382048439236052 }], [ { @@ -713,7 +719,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -724,7 +730,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -735,11 +741,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -752,9 +758,9 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test" }, "metrics": { @@ -762,10 +768,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 30167, - "start": 1730218099502131507 + "duration": 31750, + "start": 1730382048439344844 }], [ { @@ -782,7 +788,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -793,7 +799,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -804,11 +810,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -817,8 +823,8 @@ "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", - "test.status": "fail", - "test_session_id": "11758471677125334809", + "test.status": "pass", + "test_session_id": "1034076129593796617", "type": "test_session_end" }, "metrics": { @@ -826,10 +832,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 19127959, - "start": 1730218099484382632 + "duration": 22246416, + "start": 1730382048418576886 }, { "name": "test_visibility.module", @@ -845,7 +851,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -856,7 +862,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -867,7 +873,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -881,9 +887,9 @@ "test.itr.tests_skipping.enabled": "false", "test.module": "m1", "test.module_path": "", - "test.status": "fail", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", + "test.status": "pass", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", "type": "test_module_end" }, "metrics": { @@ -891,8 +897,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 17240959, - "start": 1730218099485089382 + "duration": 20740792, + "start": 1730382048418834802 }, { "name": "test_visibility.suite", @@ -908,7 +914,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -919,7 +925,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -930,7 +936,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -942,11 +948,11 @@ "test.framework_version": "1.0.0", "test.module": "m1", "test.module_path": "", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "1063630063586883636", - "test_session_id": "11758471677125334809", - "test_suite_id": "4078044225450632605", + "test_module_id": "5900551553974211565", + "test_session_id": "1034076129593796617", + "test_suite_id": "9882456734014148400", "type": "test_suite_end" }, "metrics": { @@ -954,8 +960,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 17132834, - "start": 1730218099485118132 + "duration": 20619625, + "start": 1730382048418861552 }, { "name": "test_visibility.module", @@ -971,7 +977,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -982,7 +988,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -993,7 +999,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1007,9 +1013,9 @@ "test.itr.tests_skipping.enabled": "false", "test.module": "m2", "test.module_path": "", - "test.status": "fail", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", + "test.status": "pass", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", "type": "test_module_end" }, "metrics": { @@ -1017,8 +1023,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1032666, - "start": 1730218099502379466 + "duration": 1095542, + "start": 1730382048439632802 }, { "name": "test_visibility.suite", @@ -1034,7 +1040,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1045,7 +1051,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1056,7 +1062,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1068,11 +1074,11 @@ "test.framework_version": "1.0.0", "test.module": "m2", "test.module_path": "", - "test.status": "fail", + "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test_suite_end" }, "metrics": { @@ -1080,8 +1086,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 923709, - "start": 1730218099502401132 + "duration": 979458, + "start": 1730382048439657011 }], [ { @@ -1098,7 +1104,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1109,7 +1115,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1120,11 +1126,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1137,9 +1143,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1147,12 +1153,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42791, - "start": 1730218099502420841 + "duration": 49834, + "start": 1730382048439690052 }], [ { @@ -1169,7 +1175,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1180,7 +1186,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1191,11 +1197,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1207,9 +1213,9 @@ "test.name": "m2_s1_t2", "test.status": "fail", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1217,10 +1223,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 28917, - "start": 1730218099502531007 + "duration": 33417, + "start": 1730382048439815802 }], [ { @@ -1237,7 +1243,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1248,7 +1254,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1259,25 +1265,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1285,10 +1292,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 35292, - "start": 1730218099502643799 + "duration": 35334, + "start": 1730382048439930552 }], [ { @@ -1305,7 +1312,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1316,7 +1323,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1327,25 +1334,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1353,10 +1361,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 30625, - "start": 1730218099502753049 + "duration": 34500, + "start": 1730382048440040969 }], [ { @@ -1373,7 +1381,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1384,7 +1392,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1395,25 +1403,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1421,10 +1430,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 29500, - "start": 1730218099502854882 + "duration": 40000, + "start": 1730382048440154261 }], [ { @@ -1441,7 +1450,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1452,7 +1461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1463,25 +1472,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1489,10 +1499,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 28750, - "start": 1730218099502955424 + "duration": 34209, + "start": 1730382048440271302 }], [ { @@ -1509,7 +1519,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1520,7 +1530,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1531,25 +1541,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", + "test.is_retry": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1557,10 +1568,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616 + "process_id": 80737 }, - "duration": 31833, - "start": 1730218099503067924 + "duration": 32667, + "start": 1730382048440386052 }], [ { @@ -1577,7 +1588,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6721087300000000", + "_dd.p.tid": "672388e000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1588,7 +1599,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-330/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1599,11 +1610,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.16.0.dev78+g67c849dfd.d20241029", + "library_version": "2.17.0.dev5+g019ce1525.d20241031", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "92ca6efe2c82470298cdc2520cd882ac", + "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", @@ -1617,9 +1628,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "6161152837526454920", - "test_session_id": "11758471677125334809", - "test_suite_id": "13026552725733508225", + "test_module_id": "7420958806206448377", + "test_session_id": "1034076129593796617", + "test_suite_id": "7457937023855141098", "type": "test" }, "metrics": { @@ -1627,10 +1638,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 45616, + "process_id": 80737, "test.source.end": 12, "test.source.start": 4 }, - "duration": 52042, - "start": 1730218099503189299 + "duration": 53959, + "start": 1730382048440494552 }]] From 3141c398c678ad65a61763e764242ae249352f73 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 31 Oct 2024 14:59:08 -0400 Subject: [PATCH 095/372] chore(profiling): set `OBJC_DISABLE_INITIALIZE_FORK_SAFETY` for testing native on mac (#11221) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/datadog/profiling/build_standalone.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ddtrace/internal/datadog/profiling/build_standalone.sh b/ddtrace/internal/datadog/profiling/build_standalone.sh index ae02a85d968..054ff6e9d64 100755 --- a/ddtrace/internal/datadog/profiling/build_standalone.sh +++ b/ddtrace/internal/datadog/profiling/build_standalone.sh @@ -15,6 +15,11 @@ highest_gxx="" highest_clang="" highest_clangxx="" +if [[ $OSTYPE == 'darwin'* ]]; then + # Needed for some of ForkDeathTests to pass on Mac + export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES +fi + # Function to find the highest version of compilers # Note that the product of this check is ignored if the user passes CC/CXX find_highest_compiler_version() { From 18653fab0b6833ed2b7f92b6f7867229e4d75955 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 31 Oct 2024 17:11:55 -0400 Subject: [PATCH 096/372] fix(profiling): clear finished thread ids from `ThreadSpanLinks` (#11235) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .../profiling/stack_v2/include/constants.hpp | 2 + .../stack_v2/include/thread_span_links.hpp | 8 +++ .../profiling/stack_v2/src/stack_v2.cpp | 1 + .../stack_v2/src/thread_span_links.cpp | 18 ++++-- .../stack_v2/test/thread_span_links.cpp | 50 +++++++++++++++- ...iling-span-link-leak-e7eb1aca3362cd2e.yaml | 8 +++ tests/profiling_v2/collector/test_stack.py | 60 +++++++++++++++++++ 7 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/profiling-span-link-leak-e7eb1aca3362cd2e.yaml diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/constants.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/constants.hpp index 1dd51a8e87d..3368fd0e37b 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/constants.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/constants.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "dd_wrapper/include/constants.hpp" // Default sampling frequency in microseconds. This will almost certainly be overridden by dynamic sampling. diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp index 1d61f76fe46..99bdb5cea20 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/thread_span_links.hpp @@ -21,6 +21,13 @@ struct Span , span_type(_span_type) { } + + // for testing + bool operator==(const Span& other) const + { + return span_id == other.span_id && local_root_span_id == other.local_root_span_id && + span_type == other.span_type; + } }; class ThreadSpanLinks @@ -38,6 +45,7 @@ class ThreadSpanLinks void link_span(uint64_t thread_id, uint64_t span_id, uint64_t local_root_span_id, std::string span_type); const std::optional get_active_span_from_thread_id(uint64_t thread_id); + void unlink_span(uint64_t thread_id); void reset(); static void postfork_child(); diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp index b7402ee7a2d..c56b5524bcd 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_v2.cpp @@ -86,6 +86,7 @@ stack_v2_thread_unregister(PyObject* self, PyObject* args) } Sampler::get().unregister_thread(id); + ThreadSpanLinks::get_instance().unlink_span(id); Py_RETURN_NONE; } diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp index 80c5ef06bc1..c777ff8a510 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp @@ -12,12 +12,14 @@ ThreadSpanLinks::link_span(uint64_t thread_id, uint64_t span_id, uint64_t local_ { std::lock_guard lock(mtx); - if (thread_id_to_span.find(thread_id) == thread_id_to_span.end()) { + auto it = thread_id_to_span.find(thread_id); + if (it == thread_id_to_span.end()) { thread_id_to_span[thread_id] = std::make_unique(span_id, local_root_span_id, span_type); + } else { + it->second->span_id = span_id; + it->second->local_root_span_id = local_root_span_id; + it->second->span_type = span_type; } - thread_id_to_span[thread_id]->span_id = span_id; - thread_id_to_span[thread_id]->local_root_span_id = local_root_span_id; - thread_id_to_span[thread_id]->span_type = span_type; } const std::optional @@ -33,6 +35,14 @@ ThreadSpanLinks::get_active_span_from_thread_id(uint64_t thread_id) return span; } +void +ThreadSpanLinks::unlink_span(uint64_t thread_id) +{ + std::lock_guard lock(mtx); + + thread_id_to_span.erase(thread_id); // This is a no-op if the key is not found +} + void ThreadSpanLinks::reset() { diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp index b668c3b3d80..41210dc427e 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp @@ -1,9 +1,13 @@ +#include "thread_span_links.hpp" + +#include #include +#include +#include #include #include - -#include "thread_span_links.hpp" +#include static void get() @@ -39,6 +43,48 @@ TEST(ThreadSpanLinksConcurrency, GetSetRace) t2.join(); } +TEST(ThreadSpanLinks, ClearFinished) +{ + unsigned int num_thread_ids = 100; + std::unordered_set thread_ids; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, UINT64_MAX); + + // Generate random 100 native thread ids + for (unsigned int i = 0; i < num_thread_ids; i++) { + thread_ids.insert(dis(gen)); + } + + // Call link_span with the thread ids + for (auto thread_id : thread_ids) { + Datadog::ThreadSpanLinks::get_instance().link_span(thread_id, thread_id, thread_id, "test"); + } + + std::unordered_set finished_threads; + std::uniform_real_distribution real_dis(0, 1); + + for (auto thread_id : thread_ids) { + if (real_dis(gen) < 0.5) { + finished_threads.insert(thread_id); + Datadog::ThreadSpanLinks::get_instance().unlink_span(thread_id); + } + } + + // Check that the unseen ids are removed + for (auto thread_id : thread_ids) { + std::optional span_opt = + Datadog::ThreadSpanLinks::get_instance().get_active_span_from_thread_id(thread_id); + if (finished_threads.find(thread_id) == finished_threads.end()) { + EXPECT_EQ(span_opt, Datadog::Span(thread_id, thread_id, "test")); + + } else { + EXPECT_EQ(span_opt, std::nullopt); + } + } +} + int main(int argc, char** argv) { diff --git a/releasenotes/notes/profiling-span-link-leak-e7eb1aca3362cd2e.yaml b/releasenotes/notes/profiling-span-link-leak-e7eb1aca3362cd2e.yaml new file mode 100644 index 00000000000..3129fcaa4db --- /dev/null +++ b/releasenotes/notes/profiling-span-link-leak-e7eb1aca3362cd2e.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + profiling: when a Python thread finishes, this change frees memory used for mapping + its thread id to ``Span``. The mapping is populated and used when + `DD_PROFILING_ENDPOINT_COLLECTION_ENABLED`` and + ``DD_PROFILING_STACK_V2_ENABLED`` were set to enable grouping of profiles + for endpoints. diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 571db4f8811..d86524b362c 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -1,6 +1,8 @@ import os import sys +import threading import time +from unittest.mock import patch import uuid import pytest @@ -9,6 +11,7 @@ from ddtrace import tracer from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack +from ddtrace.settings.profiling import config from tests.profiling.collector import pprof_utils @@ -111,6 +114,63 @@ def test_push_span(stack_v2_enabled, tmp_path): ) +def test_push_span_unregister_thread(tmp_path, monkeypatch): + if sys.version_info[:2] == (3, 7): + pytest.skip("stack_v2 is not supported on Python 3.7") + + with patch("ddtrace.internal.datadog.profiling.stack_v2.unregister_thread") as unregister_thread: + monkeypatch.setattr(config.stack, "v2_enabled", True) + tracer._endpoint_call_counter_span_processor.enable() + + test_name = "test_push_span_unregister_thread" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + + def target_fun(): + for _ in range(5): + time.sleep(0.01) + + with stack.StackCollector( + None, + tracer=tracer, + endpoint_collection_enabled=True, + ignore_profiler=True, # this is not necessary, but it's here to trim samples + _stack_collector_v2_enabled=True, + ): + with tracer.trace("foobar", resource=resource, span_type=span_type) as span: + span_id = span.span_id + local_root_span_id = span._local_root.span_id + t = threading.Thread(target=target_fun) + t.start() + t.join() + thread_id = t.ident + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "span id") + assert len(samples) > 0 + for sample in samples: + pprof_utils.assert_stack_event( + profile, + sample, + expected_event=pprof_utils.StackEvent( + span_id=span_id, + local_root_span_id=local_root_span_id, + trace_type=span_type, + trace_endpoint=resource, + ), + ) + + unregister_thread.assert_called_once_with(thread_id) + + @pytest.mark.parametrize("stack_v2_enabled", [True, False]) def test_push_non_web_span(stack_v2_enabled, tmp_path): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: From 223c261250ef1bd1a7547cd0d6311efc153a2395 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 31 Oct 2024 21:18:07 -0400 Subject: [PATCH 097/372] fix(profiling): call before_flush() before calling ddup.upload() (#11258) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/profiling/collector/memalloc.py | 23 ++++++++------- ddtrace/profiling/scheduler.py | 11 +++---- ...ling-live-heap-libdd-49ff3a5d6405fd7e.yaml | 7 +++++ tests/profiling_v2/collector/test_memalloc.py | 29 +++++++++++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/profiling-live-heap-libdd-49ff3a5d6405fd7e.yaml create mode 100644 tests/profiling_v2/collector/test_memalloc.py diff --git a/ddtrace/profiling/collector/memalloc.py b/ddtrace/profiling/collector/memalloc.py index 2605fe9ff85..62d4b059214 100644 --- a/ddtrace/profiling/collector/memalloc.py +++ b/ddtrace/profiling/collector/memalloc.py @@ -4,6 +4,7 @@ import os import threading import typing # noqa:F401 +from typing import Optional try: @@ -69,20 +70,22 @@ def __init__( self, recorder: Recorder, _interval: float = _DEFAULT_INTERVAL, - _max_events: int = config.memory.events_buffer, - max_nframe: int = config.max_frames, - heap_sample_size: int = config.heap.sample_size, - ignore_profiler: bool = config.ignore_profiler, - _export_libdd_enabled: bool = config.export.libdd_enabled, + _max_events: Optional[int] = None, + max_nframe: Optional[int] = None, + heap_sample_size: Optional[int] = None, + ignore_profiler: Optional[bool] = None, + _export_libdd_enabled: Optional[bool] = None, ): super().__init__(recorder=recorder) self._interval: float = _interval # TODO make this dynamic based on the 1. interval and 2. the max number of events allowed in the Recorder - self._max_events: int = _max_events - self.max_nframe: int = max_nframe - self.heap_sample_size: int = heap_sample_size - self.ignore_profiler: bool = ignore_profiler - self._export_libdd_enabled: bool = _export_libdd_enabled + self._max_events: int = _max_events if _max_events is not None else config.memory.events_buffer + self.max_nframe: int = max_nframe if max_nframe is not None else config.max_frames + self.heap_sample_size: int = heap_sample_size if heap_sample_size is not None else config.heap.sample_size + self.ignore_profiler: bool = ignore_profiler if ignore_profiler is not None else config.ignore_profiler + self._export_libdd_enabled: bool = ( + _export_libdd_enabled if _export_libdd_enabled is not None else config.export.libdd_enabled + ) def _start_service(self): # type: (...) -> None diff --git a/ddtrace/profiling/scheduler.py b/ddtrace/profiling/scheduler.py index 7d9690c29fd..9f286f8688b 100644 --- a/ddtrace/profiling/scheduler.py +++ b/ddtrace/profiling/scheduler.py @@ -52,6 +52,12 @@ def flush(self): # type: (...) -> None """Flush events from recorder to exporters.""" LOG.debug("Flushing events") + if self.before_flush is not None: + try: + self.before_flush() + except Exception: + LOG.error("Scheduler before_flush hook failed", exc_info=True) + if self._export_libdd_enabled: ddup.upload() @@ -61,11 +67,6 @@ def flush(self): self._last_export = compat.time_ns() return - if self.before_flush is not None: - try: - self.before_flush() - except Exception: - LOG.error("Scheduler before_flush hook failed", exc_info=True) events: EventsType = {} if self.recorder: events = self.recorder.reset() diff --git a/releasenotes/notes/profiling-live-heap-libdd-49ff3a5d6405fd7e.yaml b/releasenotes/notes/profiling-live-heap-libdd-49ff3a5d6405fd7e.yaml new file mode 100644 index 00000000000..1304b324de5 --- /dev/null +++ b/releasenotes/notes/profiling-live-heap-libdd-49ff3a5d6405fd7e.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + profiling: fixes an issue where enabling native exporter via + ``DD_PROFILING_EXPORT_LIBDD_ENABLED``, ``DD_PROFILING_TIMELINE_ENABLED`` + or ``DD_PROFILING_STACK_V2_ENABLED`` turned off live heap profiling. + diff --git a/tests/profiling_v2/collector/test_memalloc.py b/tests/profiling_v2/collector/test_memalloc.py new file mode 100644 index 00000000000..49e560bbf83 --- /dev/null +++ b/tests/profiling_v2/collector/test_memalloc.py @@ -0,0 +1,29 @@ +import os + +from ddtrace.profiling import Profiler +from ddtrace.settings.profiling import config +from tests.profiling.collector import pprof_utils + + +def _allocate_1k(): + return [object() for _ in range(1000)] + + +def test_heap_samples_collected(tmp_path, monkeypatch): + # Test for https://github.com/DataDog/dd-trace-py/issues/11069 + test_name = "test_heap" + pprof_prefix = str(tmp_path / test_name) + monkeypatch.setattr(config, "output_pprof", pprof_prefix) + monkeypatch.setattr(config, "max_frames", 32) + monkeypatch.setattr(config.memory, "events_buffer", 10) + monkeypatch.setattr(config.heap, "sample_size", 1024) + output_filename = pprof_prefix + "." + str(os.getpid()) + + p = Profiler() + p.start() + x = _allocate_1k() # noqa: F841 + p.stop() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_value_type(profile, "heap-space") + assert len(samples) > 0 From bc3da204abbea82e715a3f8ac7190de3cc1197e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:24:21 +0000 Subject: [PATCH 098/372] chore: update bottle latest version to 0.13.2 (#11276) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/1280196.txt | 4 ++-- .riot/requirements/15dee3b.txt | 10 +++++----- .riot/requirements/18b1b66.txt | 2 +- .riot/requirements/573fdbf.txt | 12 ++++++------ .riot/requirements/760d56e.txt | 4 ++-- .riot/requirements/8c110bf.txt | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.riot/requirements/1280196.txt b/.riot/requirements/1280196.txt index 55d4fd22d35..5d7762ec497 100644 --- a/.riot/requirements/1280196.txt +++ b/.riot/requirements/1280196.txt @@ -6,7 +6,7 @@ # attrs==24.2.0 beautifulsoup4==4.12.3 -bottle==0.13.1 +bottle==0.13.2 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 @@ -24,6 +24,6 @@ sortedcontainers==2.4.0 soupsieve==2.6 tomli==2.0.2 waitress==3.0.0 -webob==1.8.8 +webob==1.8.9 webtest==3.0.1 zipp==3.20.2 diff --git a/.riot/requirements/15dee3b.txt b/.riot/requirements/15dee3b.txt index 2b8691e8510..8adc0cbd035 100644 --- a/.riot/requirements/15dee3b.txt +++ b/.riot/requirements/15dee3b.txt @@ -7,7 +7,7 @@ attrs==24.2.0 beautifulsoup4==4.12.3 bottle==0.12.25 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -17,13 +17,13 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 soupsieve==2.6 tomli==2.0.2 -waitress==3.0.0 -webob==1.8.8 +waitress==3.0.1 +webob==1.8.9 webtest==3.0.1 zipp==3.20.2 diff --git a/.riot/requirements/18b1b66.txt b/.riot/requirements/18b1b66.txt index 222f1e2b517..606ca2c57c8 100644 --- a/.riot/requirements/18b1b66.txt +++ b/.riot/requirements/18b1b66.txt @@ -25,6 +25,6 @@ soupsieve==2.4.1 tomli==2.0.1 typing-extensions==4.7.1 waitress==2.1.2 -webob==1.8.8 +webob==1.8.9 webtest==3.0.1 zipp==3.15.0 diff --git a/.riot/requirements/573fdbf.txt b/.riot/requirements/573fdbf.txt index d6aff21e2ac..f829c5b8991 100644 --- a/.riot/requirements/573fdbf.txt +++ b/.riot/requirements/573fdbf.txt @@ -6,8 +6,8 @@ # attrs==24.2.0 beautifulsoup4==4.12.3 -bottle==0.13.1 -coverage[toml]==7.6.1 +bottle==0.13.2 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -17,13 +17,13 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 soupsieve==2.6 tomli==2.0.2 -waitress==3.0.0 -webob==1.8.8 +waitress==3.0.1 +webob==1.8.9 webtest==3.0.1 zipp==3.20.2 diff --git a/.riot/requirements/760d56e.txt b/.riot/requirements/760d56e.txt index d3e80a7a669..7daa0bd0e98 100644 --- a/.riot/requirements/760d56e.txt +++ b/.riot/requirements/760d56e.txt @@ -6,7 +6,7 @@ # attrs==24.2.0 beautifulsoup4==4.12.3 -bottle==0.13.1 +bottle==0.13.2 coverage[toml]==7.2.7 exceptiongroup==1.2.2 hypothesis==6.45.0 @@ -25,6 +25,6 @@ soupsieve==2.4.1 tomli==2.0.1 typing-extensions==4.7.1 waitress==2.1.2 -webob==1.8.8 +webob==1.8.9 webtest==3.0.1 zipp==3.15.0 diff --git a/.riot/requirements/8c110bf.txt b/.riot/requirements/8c110bf.txt index 0a57b2a151b..846c454d6d3 100644 --- a/.riot/requirements/8c110bf.txt +++ b/.riot/requirements/8c110bf.txt @@ -24,6 +24,6 @@ sortedcontainers==2.4.0 soupsieve==2.6 tomli==2.0.2 waitress==3.0.0 -webob==1.8.8 +webob==1.8.9 webtest==3.0.1 zipp==3.20.2 From 129e51b58b4fd6d1bee0ee694f6bcacd735c4b69 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:32:18 +0100 Subject: [PATCH 099/372] chore(ci): stop using macos 12 (#11277) Addresses https://github.com/actions/runner-images/issues/10721 deprecating macos-12 images. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/build_python_3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index 86f952dff47..f00de62e2b7 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -33,7 +33,7 @@ jobs: cibuildwheel --print-build-identifiers --platform linux --arch x86_64,i686 | jq -cR '{only: ., os: "ubuntu-latest"}' \ && cibuildwheel --print-build-identifiers --platform linux --arch aarch64 | jq -cR '{only: ., os: "arm-4core-linux"}' \ && cibuildwheel --print-build-identifiers --platform windows --arch AMD64,x86 | jq -cR '{only: ., os: "windows-latest"}' \ - && cibuildwheel --print-build-identifiers --platform macos --arch x86_64,universal2 | jq -cR '{only: ., os: "macos-12"}' + && cibuildwheel --print-build-identifiers --platform macos --arch x86_64,universal2 | jq -cR '{only: ., os: "macos-13"}' } | jq -sc ) echo $MATRIX_INCLUDE From 6b73e732900fd559eeabdf8c1a0e774f6bdfab43 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 4 Nov 2024 17:09:12 -0500 Subject: [PATCH 100/372] fix(profiling): remove usage of `ensure_binary_or_empty` from ddup (#11206) `ensure_binary_or_empty()` calls can incur significant memory allocations on CPython versions before 3.12 for stack v1. See below images, and all services shown here are with stack v1 with libdatadog exporter (`DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`). Python 3.11.x 237MiB/713MiB (33% of the profile) image Python 3.9.x 209MiB/595MiB (35%) image Older CPython versions seem to have a less efficient implementation of `str.encode()` which is used by `ensure_binary_or_empty()`. `str.encode()` is implemented in C so we don't show any frame below `ensure_binary()` in above images as Python profiler can't profile native code. This function is used across all profilers (stack, memory, and lock). Though enabling stack v2 implementation via `DD_PROFILING_STACK_V2_ENABLED` could remove most of it, since the function is still going to be used for memory and lock profilers, we fix it here. We use CPython `PyUnicode_AsUTF8AndSize` to retrieve raw pointer for the given Python `str` object, then create `std::string_view` to pass that over to the `Sample`. We don't propagate any information if the string is not in UTF-8. This behavior is ok for now as libdatadog exporter only accepts utf-8, using `std::str::from_utf8`. See below image for comparison of memory allocations before and after this change with Python 3.11.x. The relative and absolute amount is different from above examples, but still show a sizable reduction. Before: 252MiB/1.26GiB (20%) image After: 0MiB/1.02GiB, memory allocation from `ensure_binary_or_empty()` is just completely gone image ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/datadog/profiling/ddup/_ddup.pyx | 368 ++++++++++++------ ...reduce-memory-allocs-7b3d80adf0d2d0e7.yaml | 6 + tests/profiling/collector/pprof_utils.py | 204 +++++----- tests/profiling_v2/collector/test_memalloc.py | 2 - tests/profiling_v2/collector/test_stack.py | 327 +++++++++++++++- 5 files changed, 677 insertions(+), 230 deletions(-) create mode 100644 releasenotes/notes/profiling-reduce-memory-allocs-7b3d80adf0d2d0e7.yaml diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx index 976605f1ccd..4b4cc6551ce 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx @@ -6,6 +6,7 @@ from typing import Dict from typing import Optional from typing import Union +from cpython.unicode cimport PyUnicode_AsUTF8AndSize from libcpp.map cimport map from libcpp.unordered_map cimport unordered_map from libcpp.utility cimport pair @@ -13,7 +14,6 @@ from libcpp.utility cimport pair import ddtrace import platform from .._types import StringType -from ..util import ensure_binary_or_empty from ..util import sanitize_string from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.packages import get_distributions @@ -21,6 +21,8 @@ from ddtrace.internal.runtime import get_runtime_id from ddtrace._trace.span import Span +ctypedef void (*func_ptr_t)(string_view) + cdef extern from "stdint.h": ctypedef unsigned long long uint64_t ctypedef long long int64_t @@ -85,33 +87,230 @@ cdef extern from "code_provenance_interface.hpp": void code_provenance_add_packages(unordered_map[string_view, string_view] packages) # Create wrappers for cython -cdef call_ddup_config_service(bytes service): - ddup_config_service(string_view(service, len(service))) - -cdef call_ddup_config_env(bytes env): - ddup_config_env(string_view(env, len(env))) - -cdef call_ddup_config_version(bytes version): - ddup_config_version(string_view(version, len(version))) - -cdef call_ddup_config_url(bytes url): - ddup_config_url(string_view(url, len(url))) - -cdef call_ddup_config_runtime(bytes runtime): - ddup_config_runtime(string_view(runtime, len(runtime))) +cdef call_func_with_str(func_ptr_t func, str_arg: StringType): + if not str_arg: + return + if isinstance(str_arg, bytes): + func(string_view(str_arg, len(str_arg))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(str_arg, &utf8_size) + if utf8_data != NULL: + func(string_view(utf8_data, utf8_size)) + +cdef call_ddup_config_user_tag(key: StringType, val: StringType): + if not key or not val: + return + if isinstance(key, bytes) and isinstance(val, bytes): + ddup_config_user_tag(string_view(key, len(key)), string_view(val, len(val))) + return + cdef const char* key_utf8_data + cdef Py_ssize_t key_utf8_size + cdef const char* val_utf8_data + cdef Py_ssize_t val_utf8_size + key_utf8_data = PyUnicode_AsUTF8AndSize(key, &key_utf8_size) + val_utf8_data = PyUnicode_AsUTF8AndSize(val, &val_utf8_size) + if key_utf8_data != NULL and val_utf8_data != NULL: + ddup_config_user_tag( + string_view(key_utf8_data, key_utf8_size), + string_view(val_utf8_data, val_utf8_size) + ) -cdef call_ddup_config_runtime_version(bytes runtime_version): - ddup_config_runtime_version(string_view(runtime_version, len(runtime_version))) +cdef call_code_provenance_add_packages(distributions): + dist_names = [] + dist_versions = [] + cdef unordered_map[string_view, string_view] names_and_versions = unordered_map[string_view, string_view]() -cdef call_ddup_config_profiler_version(bytes profiler_version): - ddup_config_profiler_version(string_view(profiler_version, len(profiler_version))) + cdef const char* dist_name_utf8_data + cdef Py_ssize_t dist_name_utf8_size + cdef const char* dist_version_utf8_data + cdef Py_ssize_t dist_version_utf8_size + + for dist in distributions: + dist_name = dist.name + dist_version = dist.version + if not dist_name or not dist_version: + continue + dist_names.append(dist_name) + dist_versions.append(dist_version) + if isinstance(dist_name, bytes) and isinstance(dist_version, bytes): + names_and_versions.insert( + pair[string_view, string_view]( + string_view(dist_name, len(dist_name)), + string_view(dist_version, len(dist_version)) + ) + ) + continue + dist_name_utf8_data = PyUnicode_AsUTF8AndSize(dist_name, &dist_name_utf8_size) + dist_version_utf8_data = PyUnicode_AsUTF8AndSize(dist_version, &dist_version_utf8_size) + if dist_name_utf8_data != NULL and dist_version_utf8_data != NULL: + names_and_versions.insert( + pair[string_view, string_view]( + string_view(dist_name_utf8_data, dist_name_utf8_size), + string_view(dist_version_utf8_data, dist_version_utf8_size) + ) + ) + code_provenance_add_packages(names_and_versions) -cdef call_ddup_config_user_tag(bytes key, bytes val): - ddup_config_user_tag(string_view(key, len(key)), string_view(val, len(val))) +cdef call_ddup_profile_set_endpoints(endpoint_to_span_ids): + # We want to make sure that endpoint strings outlive the for loop below + # and prevent them to be GC'ed. We do this by storing them in a list. + # This is necessary because we pass string_views to the C++ code, which is + # a view into the original string. If the original string is GC'ed, the view + # will point to garbage. + endpoint_list = [] + cdef map[int64_t, string_view] span_ids_to_endpoints = map[int64_t, string_view]() + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + for endpoint, span_ids in endpoint_to_span_ids.items(): + if not endpoint: + continue + endpoint_list.append(endpoint) + if isinstance(endpoint, bytes): + for span_id in span_ids: + span_ids_to_endpoints.insert( + pair[int64_t, string_view]( + clamp_to_uint64_unsigned(span_id), + string_view(endpoint, len(endpoint)) + ) + ) + continue + utf8_data = PyUnicode_AsUTF8AndSize(endpoint, &utf8_size) + if utf8_data != NULL: + for span_id in span_ids: + span_ids_to_endpoints.insert( + pair[int64_t, string_view]( + clamp_to_uint64_unsigned(span_id), + string_view(utf8_data, utf8_size) + ) + ) + ddup_profile_set_endpoints(span_ids_to_endpoints) -cdef call_ddup_config_output_filename(bytes output_filename): - ddup_config_output_filename(string_view(output_filename, len(output_filename))) +cdef call_ddup_profile_add_endpoint_counts(endpoint_counts): + # We want to make sure that endpoint strings outlive the for loop below + # and prevent them to be GC'ed. We do this by storing them in a list. + # This is necessary because we pass string_views to the C++ code, which is + # a view into the original string. If the original string is GC'ed, the view + # will point to garbage. + endpoint_list = [] + cdef map[string_view, int64_t] trace_endpoints_to_counts = map[string_view, int64_t]() + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + for endpoint, count in endpoint_counts.items(): + if not endpoint: + continue + endpoint_list.append(endpoint) + if isinstance(endpoint, bytes): + trace_endpoints_to_counts.insert( + pair[string_view, int64_t]( + string_view(endpoint, len(endpoint)), + clamp_to_int64_unsigned(count) + ) + ) + continue + utf8_data = PyUnicode_AsUTF8AndSize(endpoint, &utf8_size) + if utf8_data != NULL: + trace_endpoints_to_counts.insert( + pair[string_view, int64_t]( + string_view(utf8_data, utf8_size), + clamp_to_int64_unsigned(count) + ) + ) + ddup_profile_add_endpoint_counts(trace_endpoints_to_counts) +cdef call_ddup_push_lock_name(Sample* sample, lock_name: StringType): + if not lock_name: + return + if isinstance(lock_name, bytes): + ddup_push_lock_name(sample, string_view(lock_name, len(lock_name))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(lock_name, &utf8_size) + if utf8_data != NULL: + ddup_push_lock_name(sample, string_view(utf8_data, utf8_size)) + +cdef call_ddup_push_frame(Sample* sample, name: StringType, filename: StringType, + uint64_t address, int64_t line): + if not name or not filename: + return + if isinstance(name, bytes) and isinstance(filename, bytes): + ddup_push_frame(sample, string_view(name, len(name)), + string_view(filename, len(filename)), + address, line) + return + cdef const char* name_utf8_data + cdef Py_ssize_t name_utf8_size + cdef const char* filename_utf8_data + cdef Py_ssize_t filename_utf8_size + name_utf8_data = PyUnicode_AsUTF8AndSize(name, &name_utf8_size) + filename_utf8_data = PyUnicode_AsUTF8AndSize(filename, &filename_utf8_size) + if name_utf8_data != NULL and filename_utf8_data != NULL: + ddup_push_frame(sample, string_view(name_utf8_data, name_utf8_size), + string_view(filename_utf8_data, filename_utf8_size), + address, line) + +cdef call_ddup_push_threadinfo(Sample* sample, int64_t thread_id, int64_t thread_native_id, thread_name: StringType): + if not thread_name: + return + if isinstance(thread_name, bytes): + ddup_push_threadinfo( + sample, thread_id, thread_native_id, string_view(thread_name, len(thread_name))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(thread_name, &utf8_size) + if utf8_data != NULL: + ddup_push_threadinfo(sample, thread_id, thread_native_id, string_view(utf8_data, utf8_size)) + +cdef call_ddup_push_task_name(Sample* sample, task_name: StringType): + if not task_name: + return + if isinstance(task_name, bytes): + ddup_push_task_name(sample, string_view(task_name, len(task_name))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(task_name, &utf8_size) + if utf8_data != NULL: + ddup_push_task_name(sample, string_view(utf8_data, utf8_size)) + +cdef call_ddup_push_exceptioninfo(Sample* sample, exception_name: StringType, uint64_t count): + if not exception_name: + return + if isinstance(exception_name, bytes): + ddup_push_exceptioninfo(sample, string_view(exception_name, len(exception_name)), count) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(exception_name, &utf8_size) + if utf8_data != NULL: + ddup_push_exceptioninfo(sample, string_view(utf8_data, utf8_size), count) + +cdef call_ddup_push_class_name(Sample* sample, class_name: StringType): + if not class_name: + return + if isinstance(class_name, bytes): + ddup_push_class_name(sample, string_view(class_name, len(class_name))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(class_name, &utf8_size) + if utf8_data != NULL: + ddup_push_class_name(sample, string_view(utf8_data, utf8_size)) + +cdef call_ddup_push_trace_type(Sample* sample, trace_type: StringType): + if not trace_type: + return + if isinstance(trace_type, bytes): + ddup_push_trace_type(sample, string_view(trace_type, len(trace_type))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(trace_type, &utf8_size) + if utf8_data != NULL: + ddup_push_trace_type(sample, string_view(utf8_data, utf8_size)) # Conversion functions cdef uint64_t clamp_to_uint64_unsigned(value): @@ -148,61 +347,41 @@ def config( # Try to provide a ddtrace-specific default service if one is not given service = service or DEFAULT_SERVICE_NAME - call_ddup_config_service(ensure_binary_or_empty(service)) + call_func_with_str(ddup_config_service, service) # Empty values are auto-populated in the backend (omitted in client) if env: - call_ddup_config_env(ensure_binary_or_empty(env)) + call_func_with_str(ddup_config_env, env) if version: - call_ddup_config_version(ensure_binary_or_empty(version)) + call_func_with_str(ddup_config_version, version) if url: - call_ddup_config_url(ensure_binary_or_empty(url)) + call_func_with_str(ddup_config_url, url) if output_filename: - call_ddup_config_output_filename(ensure_binary_or_empty(output_filename)) + call_func_with_str(ddup_config_output_filename, output_filename) # Inherited - call_ddup_config_runtime(ensure_binary_or_empty(platform.python_implementation())) - call_ddup_config_runtime_version(ensure_binary_or_empty(platform.python_version())) - call_ddup_config_profiler_version(ensure_binary_or_empty(ddtrace.__version__)) + call_func_with_str(ddup_config_runtime, platform.python_implementation()) + call_func_with_str(ddup_config_runtime_version, platform.python_version()) + call_func_with_str(ddup_config_profiler_version, ddtrace.__version__) if max_nframes is not None: ddup_config_max_nframes(clamp_to_int64_unsigned(max_nframes)) if tags is not None: for key, val in tags.items(): if key and val: - call_ddup_config_user_tag(ensure_binary_or_empty(key), ensure_binary_or_empty(val)) + call_ddup_config_user_tag(key, val) if timeline_enabled is True: ddup_config_timeline(True) if sample_pool_capacity: ddup_config_sample_pool_capacity(clamp_to_uint64_unsigned(sample_pool_capacity)) - # cdef not allowed in if block, so we have to do this here - cdef unordered_map[string_view, string_view] names_and_versions = unordered_map[string_view, string_view]() - # Keep these here to prevent GC from collecting them - dist_names = [] - dist_versions = [] if enable_code_provenance: code_provenance_enable(enable_code_provenance) - version_bytes = ensure_binary_or_empty(platform.python_version()) - code_provenance_set_runtime_version( - string_view(version_bytes, len(version_bytes)) - ) + call_func_with_str(code_provenance_set_runtime_version, platform.python_version()) # DEV: Do we also have to pass platsdlib_path, purelib_path, platlib_path? - stdlib_path_bytes = ensure_binary_or_empty(sysconfig.get_path("stdlib")) - code_provenance_set_stdlib_path( - string_view(stdlib_path_bytes, len(stdlib_path_bytes)) - ) - distributions = get_distributions() - for dist in distributions: - dist_name = ensure_binary_or_empty(dist.name) - dist_version = ensure_binary_or_empty(dist.version) - dist_names.append(dist_name) - dist_versions.append(dist_version) - names_and_versions.insert( - pair[string_view, string_view](string_view(dist_name, len(dist_name)), - string_view(dist_version, len(dist_version)))) - code_provenance_add_packages(names_and_versions) + call_func_with_str(code_provenance_set_stdlib_path, sysconfig.get_path("stdlib")) + call_code_provenance_add_packages(get_distributions()) def start() -> None: @@ -210,40 +389,13 @@ def start() -> None: def upload() -> None: - runtime_id = ensure_binary_or_empty(get_runtime_id()) - ddup_set_runtime_id(string_view(runtime_id, len(runtime_id))) + call_func_with_str(ddup_set_runtime_id, get_runtime_id()) processor = ddtrace.tracer._endpoint_call_counter_span_processor endpoint_counts, endpoint_to_span_ids = processor.reset() - # We want to make sure that the endpoint_bytes strings outlive the for loops - # below and prevent them to be GC'ed. We do this by storing them in a list. - # This is necessary because we pass string_views to the C++ code, which is - # a view into the original string. If the original string is GC'ed, the view - # will point to garbage. - endpoint_bytes_list = [] - cdef map[int64_t, string_view] span_ids_to_endpoints = map[int64_t, string_view]() - for endpoint, span_ids in endpoint_to_span_ids.items(): - endpoint_bytes = ensure_binary_or_empty(endpoint) - endpoint_bytes_list.append(endpoint_bytes) - for span_id in span_ids: - span_ids_to_endpoints.insert( - pair[int64_t, string_view]( - clamp_to_uint64_unsigned(span_id), - string_view(endpoint_bytes, len(endpoint_bytes)) - ) - ) - ddup_profile_set_endpoints(span_ids_to_endpoints) - - cdef map[string_view, int64_t] trace_endpoints_to_counts = map[string_view, int64_t]() - for endpoint, cnt in endpoint_counts.items(): - endpoint_bytes = ensure_binary_or_empty(endpoint) - endpoint_bytes_list.append(endpoint_bytes) - trace_endpoints_to_counts.insert(pair[string_view, int64_t]( - string_view(endpoint_bytes, len(endpoint_bytes)), - clamp_to_int64_unsigned(cnt) - )) - ddup_profile_add_endpoint_counts(trace_endpoints_to_counts) + call_ddup_profile_set_endpoints(endpoint_to_span_ids) + call_ddup_profile_add_endpoint_counts(endpoint_counts) with nogil: ddup_upload() @@ -286,32 +438,25 @@ cdef class SampleHandle: def push_lock_name(self, lock_name: StringType) -> None: if self.ptr is not NULL: - lock_name_bytes = ensure_binary_or_empty(lock_name) - ddup_push_lock_name(self.ptr, string_view(lock_name_bytes, len(lock_name_bytes))) + call_ddup_push_lock_name(self.ptr, lock_name) def push_frame(self, name: StringType, filename: StringType, address: int, line: int) -> None: if self.ptr is not NULL: # Customers report `name` and `filename` may be unexpected objects, so sanitize. - name_bytes = ensure_binary_or_empty(sanitize_string(name)) - filename_bytes = ensure_binary_or_empty(sanitize_string(filename)) - ddup_push_frame( - self.ptr, - string_view(name_bytes, len(name_bytes)), - string_view(filename_bytes, len(filename_bytes)), - clamp_to_uint64_unsigned(address), - clamp_to_int64_unsigned(line), - ) + sanitized_name = sanitize_string(name) + sanitized_filename = sanitize_string(filename) + call_ddup_push_frame(self.ptr, sanitized_name, sanitized_filename, + clamp_to_uint64_unsigned(address), clamp_to_int64_unsigned(line)) def push_threadinfo(self, thread_id: int, thread_native_id: int, thread_name: StringType) -> None: if self.ptr is not NULL: thread_id = thread_id if thread_id is not None else 0 thread_native_id = thread_native_id if thread_native_id is not None else 0 - thread_name_bytes = ensure_binary_or_empty(thread_name) - ddup_push_threadinfo( - self.ptr, - clamp_to_int64_unsigned(thread_id), - clamp_to_int64_unsigned(thread_native_id), - string_view(thread_name_bytes, len(thread_name_bytes)) + call_ddup_push_threadinfo( + self.ptr, + clamp_to_int64_unsigned(thread_id), + clamp_to_int64_unsigned(thread_native_id), + thread_name ) def push_task_id(self, task_id: Optional[int]) -> None: @@ -322,26 +467,20 @@ cdef class SampleHandle: def push_task_name(self, task_name: StringType) -> None: if self.ptr is not NULL: if task_name is not None: - task_name_bytes = ensure_binary_or_empty(task_name) - ddup_push_task_name(self.ptr, string_view(task_name_bytes, len(task_name_bytes))) + call_ddup_push_task_name(self.ptr, task_name) def push_exceptioninfo(self, exc_type: Union[None, bytes, str, type], count: int) -> None: if self.ptr is not NULL: exc_name = None if isinstance(exc_type, type): - exc_name = ensure_binary_or_empty(exc_type.__module__ + "." + exc_type.__name__) + exc_name = exc_type.__module__ + "." + exc_type.__name__ else: - exc_name = ensure_binary_or_empty(exc_type) - ddup_push_exceptioninfo( - self.ptr, - string_view(exc_name, len(exc_name)), - clamp_to_int64_unsigned(count) - ) + exc_name = exc_type + call_ddup_push_exceptioninfo(self.ptr, exc_name, clamp_to_uint64_unsigned(count)) def push_class_name(self, class_name: StringType) -> None: if self.ptr is not NULL: - class_name_bytes = ensure_binary_or_empty(class_name) - ddup_push_class_name(self.ptr, string_view(class_name_bytes, len(class_name_bytes))) + call_ddup_push_class_name(self.ptr, class_name) def push_span(self, span: Optional[Span]) -> None: if self.ptr is NULL: @@ -355,8 +494,7 @@ cdef class SampleHandle: if span._local_root.span_id: ddup_push_local_root_span_id(self.ptr, clamp_to_uint64_unsigned(span._local_root.span_id)) if span._local_root.span_type: - span_type_bytes = ensure_binary_or_empty(span._local_root.span_type) - ddup_push_trace_type(self.ptr, string_view(span_type_bytes, len(span_type_bytes))) + call_ddup_push_trace_type(self.ptr, span._local_root.span_type) def push_monotonic_ns(self, monotonic_ns: int) -> None: if self.ptr is not NULL: diff --git a/releasenotes/notes/profiling-reduce-memory-allocs-7b3d80adf0d2d0e7.yaml b/releasenotes/notes/profiling-reduce-memory-allocs-7b3d80adf0d2d0e7.yaml new file mode 100644 index 00000000000..01393fdf075 --- /dev/null +++ b/releasenotes/notes/profiling-reduce-memory-allocs-7b3d80adf0d2d0e7.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + profiling: fixes an issue where the profiler was allocating too much memory + from ``ensure_binary_or_empty()`` function, on Python versions before 3.12, + with ``DD_PROFILING_EXPORT_LIBDD_ENABLED`` or ``DD_PROFILING_TIMELINE_ENABLED``. diff --git a/tests/profiling/collector/pprof_utils.py b/tests/profiling/collector/pprof_utils.py index 7d2c980745e..5d5c5aab22f 100644 --- a/tests/profiling/collector/pprof_utils.py +++ b/tests/profiling/collector/pprof_utils.py @@ -3,7 +3,11 @@ import glob import os import re -import typing +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Union import lz4.frame @@ -12,6 +16,7 @@ UINT64_MAX = (1 << 64) - 1 +DEBUG_TEST = False # Clamp the value to the range [0, UINT64_MAX] as done in clamp_to_uint64_unsigned @@ -30,9 +35,13 @@ def reinterpret_int_as_int64(value: int) -> int: class StackLocation: - def __init__(self, function_name: str, filename: str): + def __init__(self, function_name: str, filename: str, line_no: int): self.function_name = function_name self.filename = filename + self.line_no = line_no + + def __repr__(self): + return f"{self.filename}:{self.function_name}:{self.line_no}" class LockEventType(Enum): @@ -43,10 +52,15 @@ class LockEventType(Enum): class EventBaseClass: def __init__( self, - span_id: typing.Optional[int] = None, - local_root_span_id: typing.Optional[int] = None, - trace_type: typing.Optional[str] = None, - trace_endpoint: typing.Optional[str] = None, + span_id: Optional[int] = None, + local_root_span_id: Optional[int] = None, + trace_type: Optional[str] = None, + trace_endpoint: Optional[str] = None, + thread_id: Union[int, None] = None, + thread_name: Union[str, None] = None, + class_name: Union[str, None] = None, + task_id: Union[int, None] = None, + task_name: Union[str, None] = None, ): self.span_id = reinterpret_int_as_int64(clamp_to_uint64(span_id)) if span_id else None self.local_root_span_id = ( @@ -54,11 +68,17 @@ def __init__( ) self.trace_type = trace_type self.trace_endpoint = trace_endpoint + self.thread_id = thread_id + self.thread_name = thread_name + self.class_name = class_name + self.task_id = task_id + self.task_name = task_name class StackEvent(EventBaseClass): - def __init__(self, locations: typing.Optional[typing.Any] = None, *args, **kwargs): + def __init__(self, locations: Optional[Any] = None, exception_type=None, *args, **kwargs): self.locations = locations + self.exception_type = exception_type super().__init__(*args, **kwargs) @@ -70,11 +90,7 @@ def __init__( caller_name: str, filename: str, linenos: LineNo, - lock_name: typing.Union[str, None] = None, - task_id: typing.Union[int, None] = None, - task_name: typing.Union[str, None] = None, - thread_id: typing.Union[int, None] = None, - thread_name: typing.Union[str, None] = None, + lock_name: Union[str, None] = None, *args, **kwargs, ): @@ -83,10 +99,6 @@ def __init__( self.filename = filename self.linenos = linenos self.lock_name = lock_name - self.task_id = task_id - self.task_name = task_name - self.thread_id = thread_id - self.thread_name = thread_name super().__init__(*args, **kwargs) @@ -118,16 +130,16 @@ def get_sample_type_index(profile: pprof_pb2.Profile, value_type: str) -> int: ) -def get_samples_with_value_type(profile: pprof_pb2.Profile, value_type: str) -> typing.List[pprof_pb2.Sample]: +def get_samples_with_value_type(profile: pprof_pb2.Profile, value_type: str) -> List[pprof_pb2.Sample]: value_type_idx = get_sample_type_index(profile, value_type) return [sample for sample in profile.sample if sample.value[value_type_idx] > 0] -def get_samples_with_label_key(profile: pprof_pb2.Profile, key: str) -> typing.List[pprof_pb2.Sample]: +def get_samples_with_label_key(profile: pprof_pb2.Profile, key: str) -> List[pprof_pb2.Sample]: return [sample for sample in profile.sample if get_label_with_key(profile.string_table, sample, key)] -def get_label_with_key(string_table: typing.Dict[int, str], sample: pprof_pb2.Sample, key: str) -> pprof_pb2.Label: +def get_label_with_key(string_table: Dict[int, str], sample: pprof_pb2.Sample, key: str) -> pprof_pb2.Label: return next((label for label in sample.label if string_table[label.key] == key), None) @@ -141,7 +153,7 @@ def get_function_with_id(profile: pprof_pb2.Profile, function_id: int) -> pprof_ def assert_lock_events_of_type( profile: pprof_pb2.Profile, - expected_events: typing.List[LockEvent], + expected_events: List[LockEvent], event_type: LockEventType, ): samples = get_samples_with_value_type( @@ -171,8 +183,8 @@ def assert_lock_events_of_type( def assert_lock_events( profile: pprof_pb2.Profile, - expected_acquire_events: typing.Union[typing.List[LockEvent], None] = None, - expected_release_events: typing.Union[typing.List[LockEvent], None] = None, + expected_acquire_events: Union[List[LockEvent], None] = None, + expected_release_events: Union[List[LockEvent], None] = None, ): if expected_acquire_events: assert_lock_events_of_type(profile, expected_acquire_events, LockEventType.ACQUIRE) @@ -180,34 +192,33 @@ def assert_lock_events( assert_lock_events_of_type(profile, expected_release_events, LockEventType.RELEASE) -def assert_base_event(profile, sample: pprof_pb2.Sample, expected_event: EventBaseClass): - if expected_event.span_id is not None: - span_id_label = get_label_with_key(profile.string_table, sample, "span id") - assert span_id_label.num == expected_event.span_id, "Expected span_id {} got {}".format( - expected_event.span_id, span_id_label.num +def assert_str_label(string_table, sample, key: str, expected_value: Optional[str]): + if expected_value: + label = get_label_with_key(string_table, sample, key) + # We use fullmatch to ensure that the whole string matches the expected value + # and not just a substring + assert label is not None, "Label {} not found in sample".format(key) + assert re.fullmatch(expected_value, string_table[label.str]), "Expected {} got {} for label {}".format( + expected_value, string_table[label.str], key ) - if expected_event.local_root_span_id is not None: - local_root_span_id_label = get_label_with_key(profile.string_table, sample, "local root span id") - assert ( - local_root_span_id_label.num == expected_event.local_root_span_id - ), "Expected local_root_span_id {} got {}".format( - expected_event.local_root_span_id, local_root_span_id_label.num - ) - if expected_event.trace_type is not None: - trace_type_label = get_label_with_key(profile.string_table, sample, "trace type") - assert ( - profile.string_table[trace_type_label.str] == expected_event.trace_type - ), "Expected trace_type {} got {}".format(expected_event.trace_type, profile.string_table[trace_type_label.str]) - - if expected_event.trace_endpoint is not None: - trace_endpoint_label = get_label_with_key(profile.string_table, sample, "trace endpoint") - assert ( - profile.string_table[trace_endpoint_label.str] == expected_event.trace_endpoint - ), "Expected trace endpoint {} got {}".format( - expected_event.trace_endpoint, profile.string_table[trace_endpoint_label.str] - ) +def assert_num_label(string_table, sample, key: str, expected_value: Optional[int]): + if expected_value: + label = get_label_with_key(string_table, sample, key) + assert label.num == expected_value, "Expected {} got {} for label {}".format(expected_value, label.num, key) + + +def assert_base_event(profile, sample: pprof_pb2.Sample, expected_event: EventBaseClass): + assert_num_label(profile.string_table, sample, "span id", expected_event.span_id) + assert_num_label(profile.string_table, sample, "local root span id", expected_event.local_root_span_id) + assert_str_label(profile.string_table, sample, "trace type", expected_event.trace_type) + assert_str_label(profile.string_table, sample, "trace endpoint", expected_event.trace_endpoint) + assert_num_label(profile.string_table, sample, "thread id", expected_event.thread_id) + assert_str_label(profile.string_table, sample, "thread name", expected_event.thread_name) + assert_str_label(profile.string_table, sample, "class name", expected_event.class_name) + assert_num_label(profile.string_table, sample, "task id", expected_event.task_id) + assert_str_label(profile.string_table, sample, "task name", expected_event.task_name) def assert_lock_event(profile, sample: pprof_pb2.Sample, expected_event: LockEvent): @@ -243,57 +254,62 @@ def assert_lock_event(profile, sample: pprof_pb2.Sample, expected_event: LockEve expected_event.linenos.release, line.line ) - if expected_event.task_id is not None: - task_id_label = get_label_with_key(profile.string_table, sample, "task id") - assert task_id_label.num == expected_event.task_id, "Expected task_id {} got {}".format( - expected_event.task_id, task_id_label.num - ) - - if expected_event.task_name is not None: - task_name_label = get_label_with_key(profile.string_table, sample, "task name") - assert re.fullmatch( - expected_event.task_name, - profile.string_table[task_name_label.str], - ), "Expected task_name {} got {}".format(expected_event.task_name, profile.string_table[task_name_label.str]) - - if expected_event.thread_id: - thread_id_label = get_label_with_key(profile.string_table, sample, "thread id") - assert thread_id_label.num == expected_event.thread_id, "Expected thread_id {} got {}".format( - expected_event.thread_id, thread_id_label.num - ) - - if expected_event.thread_name: - thread_name_label = get_label_with_key(profile.string_table, sample, "thread name") - assert ( - profile.string_table[thread_name_label.str] == expected_event.thread_name - ), "Expected thread_name {} got {}".format( - expected_event.thread_name, profile.string_table[thread_name_label.str] - ) - assert_base_event(profile, sample, expected_event) -# helper function to check whether the expected stack event is present in the samples -def has_sample_with_locations(profile, expected_locations: typing.List[StackLocation]) -> bool: - for sample_idx, sample in enumerate(profile.sample): - # in a sample there can be multiple locations, we need to check - # whether there's a consecutive subsequence of locations that match - # the expected locations - expected_location_idx = 0 - for location_id in sample.location_id: - location = get_location_with_id(profile, location_id) - function = get_function_with_id(profile, location.line[0].function_id) - function_name = profile.string_table[function.name] - filename = os.path.basename(profile.string_table[function.filename]) - if ( - function_name.endswith(expected_locations[expected_location_idx].function_name) - and filename == expected_locations[expected_location_idx].filename - ): - expected_location_idx += 1 - if expected_location_idx == len(expected_locations): - return True - return False +def assert_sample_has_locations(profile, sample, expected_locations: Optional[List[StackLocation]]): + if not expected_locations: + return + + expected_locations_idx = 0 + checked = False + + # For debug printing + sample_loc_strs = [] + # in a sample there can be multiple locations, we need to check + # whether there's a consecutive subsequence of locations that match + # the expected locations + for location_id in sample.location_id: + location = get_location_with_id(profile, location_id) + line = location.line[0] + function = get_function_with_id(profile, line.function_id) + function_name = profile.string_table[function.name] + filename = os.path.basename(profile.string_table[function.filename]) + line_no = line.line + sample_loc_strs.append(f"{filename}:{function_name}:{line_no}") + if ( + function_name.endswith(expected_locations[expected_locations_idx].function_name) + and filename == expected_locations[expected_locations_idx].filename + and line_no == expected_locations[expected_locations_idx].line_no + ): + expected_locations_idx += 1 + if expected_locations_idx == len(expected_locations): + checked = True + break + + assert checked, "Expected locations {} not found in sample locations: {}".format( + expected_locations, sample_loc_strs + ) def assert_stack_event(profile, sample: pprof_pb2.Sample, expected_event: StackEvent): + # Check that the sample has label "exception type" with value + + assert_str_label(profile.string_table, sample, "exception type", expected_event.exception_type) + assert_sample_has_locations(profile, sample, expected_event.locations) assert_base_event(profile, sample, expected_event) + + +def assert_has_samples(profile, expected_samples): + found = False + for sample in profile.sample: + try: + assert_stack_event(profile, sample, expected_samples) + found = True + break + except AssertionError as e: + # flip the flag to print the error message + if DEBUG_TEST: + print(e) + + assert found, "Expected samples not found in profile" diff --git a/tests/profiling_v2/collector/test_memalloc.py b/tests/profiling_v2/collector/test_memalloc.py index 49e560bbf83..2787507bdce 100644 --- a/tests/profiling_v2/collector/test_memalloc.py +++ b/tests/profiling_v2/collector/test_memalloc.py @@ -14,8 +14,6 @@ def test_heap_samples_collected(tmp_path, monkeypatch): test_name = "test_heap" pprof_prefix = str(tmp_path / test_name) monkeypatch.setattr(config, "output_pprof", pprof_prefix) - monkeypatch.setattr(config, "max_frames", 32) - monkeypatch.setattr(config.memory, "events_buffer", 10) monkeypatch.setattr(config.heap, "sample_size", 1024) output_filename = pprof_prefix + "." + str(os.getpid()) diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index d86524b362c..f5c99b2c63d 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -1,3 +1,4 @@ +import _thread import os import sys import threading @@ -16,11 +17,11 @@ @pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_stack_v2_locations(stack_v2_enabled, tmp_path): +def test_stack_locations(stack_v2_enabled, tmp_path): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: pytest.skip("stack_v2 is not supported on Python 3.7") - test_name = "test_locations" + test_name = "test_stack_locations" pprof_prefix = str(tmp_path / test_name) output_filename = pprof_prefix + "." + str(os.getpid()) @@ -46,24 +47,29 @@ def foo(): samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") assert len(samples) > 0 - expected_locations = [ - pprof_utils.StackLocation( - function_name="baz", - filename="test_stack.py", - ), - pprof_utils.StackLocation( - function_name="bar", - filename="test_stack.py", - ), - pprof_utils.StackLocation( - function_name="foo", - filename="test_stack.py", - ), - ] + expected_sample = pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + thread_name="MainThread", + locations=[ + pprof_utils.StackLocation( + function_name="baz", + filename="test_stack.py", + line_no=baz.__code__.co_firstlineno + 1, + ), + pprof_utils.StackLocation( + function_name="bar", + filename="test_stack.py", + line_no=bar.__code__.co_firstlineno + 1, + ), + pprof_utils.StackLocation( + function_name="foo", + filename="test_stack.py", + line_no=foo.__code__.co_firstlineno + 1, + ), + ], + ) - assert pprof_utils.has_sample_with_locations( - profile, expected_locations - ), "Sample with expected locations not found" + pprof_utils.assert_has_samples(profile, expected_sample) @pytest.mark.parametrize("stack_v2_enabled", [True, False]) @@ -267,3 +273,286 @@ def test_push_span_none_span_type(stack_v2_enabled, tmp_path): # trace_endpoint is not set for non-web spans ), ) + + +@pytest.mark.skipif(not stack.FEATURES["stack-exceptions"], reason="Stack exceptions are not supported") +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_exception_collection(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + test_name = "test_exception_collection" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + try: + raise ValueError("hello") + except Exception: + time.sleep(1) + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "exception type") + + if stack_v2_enabled: + # DEV: update the test once we have exception profiling for stack v2 + # using echion + assert len(samples) == 0 + else: + assert len(samples) > 0 + for sample in samples: + pprof_utils.assert_stack_event( + profile, + sample, + expected_event=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + thread_name="MainThread", + exception_type="builtins.ValueError", + locations=[ + pprof_utils.StackLocation( + filename="test_stack.py", + function_name="test_exception_collection", + line_no=test_exception_collection.__code__.co_firstlineno + 18, + ), + ], + ), + ) + + +@pytest.mark.skipif(not stack.FEATURES["stack-exceptions"], reason="Stack exceptions are not supported") +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_exception_collection_threads(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + test_name = "test_exception_collection_threads" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + + def target_fun(): + try: + raise ValueError("hello") + except Exception: + time.sleep(1) + + threads = [] + for _ in range(5): + t = threading.Thread(target=target_fun) + threads.append(t) + t.start() + + for t in threads: + t.join() + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "exception type") + + if stack_v2_enabled: + assert len(samples) == 0 + else: + assert len(samples) > 0 + for sample in samples: + thread_id_label = pprof_utils.get_label_with_key(profile.string_table, sample, "thread id") + thread_id = int(thread_id_label.num) + assert thread_id in [t.ident for t in threads] + + pprof_utils.assert_stack_event( + profile, + sample, + expected_event=pprof_utils.StackEvent( + exception_type="builtins.ValueError", + thread_name=r"Thread-\d+ \(target_fun\)" if sys.version_info[:2] > (3, 9) else r"Thread-\d+", + locations=[ + pprof_utils.StackLocation( + filename="test_stack.py", + function_name="target_fun", + line_no=target_fun.__code__.co_firstlineno + 4, + ), + ], + ), + ) + + +@pytest.mark.skipif(not stack.FEATURES["stack-exceptions"], reason="Stack exceptions are not supported") +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_exception_collection_trace(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + test_name = "test_exception_collection_trace" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + tracer._endpoint_call_counter_span_processor.enable() + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(None, tracer=tracer, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + with tracer.trace("foobar", resource="resource", span_type=ext.SpanTypes.WEB): + try: + raise ValueError("hello") + except Exception: + time.sleep(1) + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "exception type") + + if stack_v2_enabled: + assert len(samples) == 0 + else: + assert len(samples) > 0 + for sample in samples: + pprof_utils.assert_stack_event( + profile, + sample, + expected_event=pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + thread_name="MainThread", + exception_type="builtins.ValueError", + trace_type=ext.SpanTypes.WEB, + trace_endpoint="resource", + locations=[ + pprof_utils.StackLocation( + filename="test_stack.py", + function_name="test_exception_collection_trace", + line_no=test_exception_collection_trace.__code__.co_firstlineno + 21, + ), + ], + ), + ) + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_collect_once_with_class(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + class SomeClass(object): + @classmethod + def sleep_class(cls): + return cls().sleep_instance() + + def sleep_instance(self): + for _ in range(5): + time.sleep(0.01) + + test_name = "test_collect_once_with_class" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + SomeClass.sleep_class() + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") + assert len(samples) > 0 + + pprof_utils.assert_has_samples( + profile, + pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + thread_name="MainThread", + class_name="SomeClass" if not stack_v2_enabled else None, + locations=[ + pprof_utils.StackLocation( + function_name="sleep_instance", + filename="test_stack.py", + line_no=SomeClass.sleep_instance.__code__.co_firstlineno + 2, + ), + pprof_utils.StackLocation( + function_name="sleep_class", + filename="test_stack.py", + line_no=SomeClass.sleep_class.__code__.co_firstlineno + 2, + ), + pprof_utils.StackLocation( + function_name="test_collect_once_with_class", + filename="test_stack.py", + line_no=test_collect_once_with_class.__code__.co_firstlineno + 23, + ), + ], + ), + ) + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_collect_once_with_class_not_right_type(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + class SomeClass(object): + @classmethod + def sleep_class(foobar, cls): + return foobar().sleep_instance(cls) + + def sleep_instance(foobar, self): + for _ in range(5): + time.sleep(0.01) + + test_name = "test_collect_once_with_class" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + SomeClass.sleep_class(123) + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") + assert len(samples) > 0 + + pprof_utils.assert_has_samples( + profile, + pprof_utils.StackEvent( + thread_id=_thread.get_ident(), + thread_name="MainThread", + # stack v1 relied on using cls and self to figure out class name + # so we can't find it here. + class_name=None, + locations=[ + pprof_utils.StackLocation( + function_name="sleep_instance", + filename="test_stack.py", + line_no=SomeClass.sleep_instance.__code__.co_firstlineno + 2, + ), + pprof_utils.StackLocation( + function_name="sleep_class", + filename="test_stack.py", + line_no=SomeClass.sleep_class.__code__.co_firstlineno + 2, + ), + pprof_utils.StackLocation( + function_name="test_collect_once_with_class_not_right_type", + filename="test_stack.py", + line_no=test_collect_once_with_class_not_right_type.__code__.co_firstlineno + 23, + ), + ], + ), + ) From d9c859b1ea9333b08de2386a181abcf5c9d804b4 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 4 Nov 2024 21:43:17 -0500 Subject: [PATCH 101/372] test: enable crashtracking during all tests (#11282) Explicitly enable the crashtracker during all tests. We want to proactively find any new scenarios where the crash tracker does not work, or causes issues with any other products/features. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/conftest.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 20995b7b6cd..d02964f9d0b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,7 @@ from ddtrace._trace.provider import _DD_CONTEXTVAR from ddtrace.internal.compat import httplib from ddtrace.internal.compat import parse +from ddtrace.internal.core import crashtracking from ddtrace.internal.remoteconfig.client import RemoteConfigClient from ddtrace.internal.remoteconfig.worker import remoteconfig_poller from ddtrace.internal.runtime import get_runtime_id @@ -113,6 +114,19 @@ def use_global_tracer(): yield False +@pytest.fixture +def auto_enable_crashtracking(): + yield True + + +@pytest.fixture(autouse=True) +def enable_crashtracking(auto_enable_crashtracking): + if auto_enable_crashtracking: + crashtracking.start() + assert crashtracking.is_started() + yield + + @pytest.fixture def tracer(use_global_tracer): if use_global_tracer: From 629ca436071c90c400ec739f6809d760ff53e200 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:28:49 +0100 Subject: [PATCH 102/372] chore(ci_visibility): don't rely on TestShortLogReport in pytest v2 plugin (#11257) This fixes v2 of the `pytest` plugin by removing the reliance on `TestShortLogReport` (which was introduced in `7.4.x`) so that the plugin can support older versions of `pytest` (back to `7.0.0`). The [pytest_report_teststatus](https://docs.pytest.org/en/7.0.x/reference/reference.html#pytest.hookspec.pytest_report_teststatus) hook allows returning a simple tuple, so we update our hook implementations said tuple. No release note because this plugin is still unreleased. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_atr_utils.py | 18 +++++++---------- ddtrace/contrib/pytest/_efd_utils.py | 24 +++++++++-------------- ddtrace/contrib/pytest/_plugin_v2.py | 4 ++-- ddtrace/contrib/pytest/_types.py | 7 +++---- tests/ci_visibility/test_ci_visibility.py | 4 +++- 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/ddtrace/contrib/pytest/_atr_utils.py b/ddtrace/contrib/pytest/_atr_utils.py index 4ccaf554339..89be8b881af 100644 --- a/ddtrace/contrib/pytest/_atr_utils.py +++ b/ddtrace/contrib/pytest/_atr_utils.py @@ -7,8 +7,8 @@ from ddtrace.contrib.pytest._retry_utils import _get_outcome_from_retry from ddtrace.contrib.pytest._retry_utils import _get_retry_attempt_string from ddtrace.contrib.pytest._retry_utils import set_retry_num +from ddtrace.contrib.pytest._types import _pytest_report_teststatus_return_type from ddtrace.contrib.pytest._types import pytest_TestReport -from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_test_id_from_item from ddtrace.contrib.pytest._utils import _TestOutcome @@ -244,31 +244,27 @@ def atr_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.Te terminalreporter.write_sep("=", purple=True, bold=True) -def atr_get_teststatus(report: pytest_TestReport) -> t.Optional[pytest_TestShortLogReport]: +def atr_get_teststatus(report: pytest_TestReport) -> _pytest_report_teststatus_return_type: if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED: - return pytest.TestShortLogReport( + return ( _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, "r", (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}PASSED", {"green": True}), ) if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED: - return pytest.TestShortLogReport( + return ( _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, "R", (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}FAILED", {"yellow": True}), ) if report.outcome == _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED: - return pytest.TestShortLogReport( + return ( _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, "s", (f"ATR RETRY {_get_retry_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), ) if report.outcome == _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED: - return pytest.TestShortLogReport( - _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, ".", ("ATR FINAL STATUS: PASSED", {"green": True}) - ) + return (_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, ".", ("ATR FINAL STATUS: PASSED", {"green": True})) if report.outcome == _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED: - return pytest.TestShortLogReport( - _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, "F", ("ATR FINAL STATUS: FAILED", {"red": True}) - ) + return (_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, "F", ("ATR FINAL STATUS: FAILED", {"red": True})) return None diff --git a/ddtrace/contrib/pytest/_efd_utils.py b/ddtrace/contrib/pytest/_efd_utils.py index ad94adec619..1e16934bb11 100644 --- a/ddtrace/contrib/pytest/_efd_utils.py +++ b/ddtrace/contrib/pytest/_efd_utils.py @@ -7,8 +7,8 @@ from ddtrace.contrib.pytest._retry_utils import _get_outcome_from_retry from ddtrace.contrib.pytest._retry_utils import _get_retry_attempt_string from ddtrace.contrib.pytest._retry_utils import set_retry_num +from ddtrace.contrib.pytest._types import _pytest_report_teststatus_return_type from ddtrace.contrib.pytest._types import pytest_TestReport -from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_test_id_from_item from ddtrace.contrib.pytest._utils import _TestOutcome @@ -304,39 +304,33 @@ def efd_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.Te terminalreporter.write_sep("=", purple=True, bold=True) -def efd_get_teststatus(report: pytest_TestReport) -> t.Optional[pytest_TestShortLogReport]: +def efd_get_teststatus(report: pytest_TestReport) -> _pytest_report_teststatus_return_type: if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED: - return pytest.TestShortLogReport( + return ( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_PASSED, "r", (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}PASSED", {"green": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED: - return pytest.TestShortLogReport( + return ( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_FAILED, "R", (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}FAILED", {"yellow": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED: - return pytest.TestShortLogReport( + return ( _EFD_RETRY_OUTCOMES.EFD_ATTEMPT_SKIPPED, "s", (f"EFD RETRY {_get_retry_attempt_string(report.nodeid)}SKIPPED", {"yellow": True}), ) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED: - return pytest.TestShortLogReport( - _EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED, ".", ("EFD FINAL STATUS: PASSED", {"green": True}) - ) + return (_EFD_RETRY_OUTCOMES.EFD_FINAL_PASSED, ".", ("EFD FINAL STATUS: PASSED", {"green": True})) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED: - return pytest.TestShortLogReport( - _EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED, "F", ("EFD FINAL STATUS: FAILED", {"red": True}) - ) + return (_EFD_RETRY_OUTCOMES.EFD_FINAL_FAILED, "F", ("EFD FINAL STATUS: FAILED", {"red": True})) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED: - return pytest.TestShortLogReport( - _EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED, "S", ("EFD FINAL STATUS: SKIPPED", {"yellow": True}) - ) + return (_EFD_RETRY_OUTCOMES.EFD_FINAL_SKIPPED, "S", ("EFD FINAL STATUS: SKIPPED", {"yellow": True})) if report.outcome == _EFD_RETRY_OUTCOMES.EFD_FINAL_FLAKY: # Flaky tests are the only one that have a pretty string because they are intended to be displayed in the final # count of terminal summary - return pytest.TestShortLogReport(_EFD_FLAKY_OUTCOME, "K", ("EFD FINAL STATUS: FLAKY", {"yellow": True})) + return (_EFD_FLAKY_OUTCOME, "K", ("EFD FINAL STATUS: FLAKY", {"yellow": True})) return None diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 85b8399db0a..2f7816343ac 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -18,10 +18,10 @@ from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled from ddtrace.contrib.pytest._retry_utils import get_retry_num +from ddtrace.contrib.pytest._types import _pytest_report_teststatus_return_type from ddtrace.contrib.pytest._types import pytest_CallInfo from ddtrace.contrib.pytest._types import pytest_Config from ddtrace.contrib.pytest._types import pytest_TestReport -from ddtrace.contrib.pytest._types import pytest_TestShortLogReport from ddtrace.contrib.pytest._utils import PYTEST_STATUS from ddtrace.contrib.pytest._utils import _get_module_path_from_item from ddtrace.contrib.pytest._utils import _get_names_from_item @@ -562,7 +562,7 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: def pytest_report_teststatus( report: pytest_TestReport, -) -> t.Optional[pytest_TestShortLogReport]: +) -> _pytest_report_teststatus_return_type: if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): test_status = atr_get_teststatus(report) if test_status is not None: diff --git a/ddtrace/contrib/pytest/_types.py b/ddtrace/contrib/pytest/_types.py index b9f9b429729..36f34c82791 100644 --- a/ddtrace/contrib/pytest/_types.py +++ b/ddtrace/contrib/pytest/_types.py @@ -1,3 +1,5 @@ +import typing as t + from ddtrace.contrib.pytest._utils import _get_pytest_version_tuple @@ -10,7 +12,4 @@ from _pytest.reports import TestReport as pytest_TestReport # noqa: F401 from _pytest.runner import CallInfo as pytest_CallInfo # noqa: F401 -if _get_pytest_version_tuple() >= (7, 4, 0): - from pytest import TestShortLogReport as pytest_TestShortLogReport # noqa: F401 -else: - from _pytest.reports import TestReport as pytest_TestShortLogReport # noqa: F401 +_pytest_report_teststatus_return_type = t.Optional[t.Tuple[str, str, t.Tuple[str, t.Mapping[str, bool]]]] diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index 4db43a823b4..f4ef545c5bd 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -606,7 +606,9 @@ def test_civisibilitywriter_coverage_agentless_with_intake_url_param(self): cov_client = dummy_writer._clients[1] assert cov_client._intake_url == "https://citestcov-intake.datadoghq.com" - with mock.patch("ddtrace.internal.writer.writer.get_connection") as _get_connection: + with mock.patch("ddtrace.internal.writer.writer.get_connection") as _get_connection, mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig() + ): _get_connection.return_value.getresponse.return_value.status = 200 dummy_writer._put("", {}, cov_client, no_trace=True) _get_connection.assert_called_once_with("https://citestcov-intake.datadoghq.com", 2.0) From 236b60f7756480f62b4c4258f7666f7aa4847b32 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:16:44 +0100 Subject: [PATCH 103/372] ci: make fastapi test framework use newer cache action (#11289) The old cache action uses node 16 and causes deprecation warnings. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/test_frameworks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 8bc6be1cb16..bd3abf648bc 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -396,7 +396,7 @@ jobs: repository: tiangolo/fastapi ref: 0.92.0 path: fastapi - - uses: actions/cache@v3.3.1 + - uses: actions/cache@v4.1.2 if: needs.needs-run.outputs.outcome == 'success' id: cache with: From 06c97522e7abad8d04f9ba06d726a88326744cb6 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Tue, 5 Nov 2024 12:32:08 +0100 Subject: [PATCH 104/372] chore(asm): configuration endpoint as private env var (#11136) --- ddtrace/constants.py | 2 +- ddtrace/settings/asm.py | 4 +- ddtrace/settings/config.py | 5 +- ddtrace/settings/endpoint_config.py | 45 +++++++ tests/appsec/iast/conftest.py | 18 +++ .../integration/http_config_server.py | 42 ++++++ tests/appsec/iast/test_env_var.py | 42 ++++++ tests/tracer/test_endpoint_config.py | 124 ++++++++++++++++++ 8 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 ddtrace/settings/endpoint_config.py create mode 100644 tests/appsec/iast/fixtures/integration/http_config_server.py create mode 100644 tests/tracer/test_endpoint_config.py diff --git a/ddtrace/constants.py b/ddtrace/constants.py index 84fbb311f88..8361dc57e12 100644 --- a/ddtrace/constants.py +++ b/ddtrace/constants.py @@ -31,7 +31,7 @@ MULTIPLE_IP_HEADERS = "_dd.multiple-ip-headers" APPSEC_ENV = "DD_APPSEC_ENABLED" - +CONFIG_ENDPOINT_ENV = "_DD_CONFIG_ENDPOINT" IAST_ENV = "DD_IAST_ENABLED" MANUAL_DROP_KEY = "manual.drop" diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index b15ba7b7861..07ee66a5d1b 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -65,7 +65,9 @@ class ASMConfig(Env): # prevent empty string if _asm_static_rule_file == "": _asm_static_rule_file = None - _iast_enabled = Env.var(bool, IAST.ENV, default=False) + _iast_enabled = tracer_config._from_endpoint.get( # type: ignore + "iast_enabled", Env.var(bool, IAST.ENV, default=False) + ) _iast_request_sampling = Env.var(float, IAST.ENV_REQUEST_SAMPLING, default=30.0) _iast_debug = Env.var(bool, IAST.ENV_DEBUG, default=False, private=True) _iast_propagation_debug = Env.var(bool, IAST.ENV_PROPAGATION_DEBUG, default=False, private=True) diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 33105142b0b..aa442574562 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -37,6 +37,7 @@ from ..pin import Pin from ._core import get_config as _get_config from ._otel_remapper import otel_remapping as _otel_remapping +from .endpoint_config import fetch_config_from_endpoint from .http import HttpConfig from .integration import IntegrationConfig @@ -49,6 +50,7 @@ log = get_logger(__name__) +ENDPOINT_FETCHED_CONFIG = fetch_config_from_endpoint() DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT = ( r"(?ix)" @@ -384,6 +386,7 @@ def __init__(self): # Must map Otel configurations to Datadog configurations before creating the config object. _otel_remapping() # Must come before _integration_configs due to __setattr__ + self._from_endpoint = ENDPOINT_FETCHED_CONFIG self._config = _default_config() sample_rate = os.getenv("DD_TRACE_SAMPLE_RATE") @@ -763,7 +766,7 @@ def _notify_subscribers(self, changed_items): def __setattr__(self, key, value): # type: (str, Any) -> None - if key == "_config": + if key in ("_config", "_from_endpoint"): return super(self.__class__, self).__setattr__(key, value) elif key in self._config: self._set_config_items([(key, value, "code")]) diff --git a/ddtrace/settings/endpoint_config.py b/ddtrace/settings/endpoint_config.py new file mode 100644 index 00000000000..78368078b9a --- /dev/null +++ b/ddtrace/settings/endpoint_config.py @@ -0,0 +1,45 @@ +""" +This module contains the logic to configure ddtrace products from a configuration endpoint. +The configuration endpoint is a URL that returns a JSON object with the configuration for the products. +It takes precedence over environment variables and configuration files. +""" +import json +import os +from urllib import parse + +from ddtrace.constants import CONFIG_ENDPOINT_ENV +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.http import connector + + +log = get_logger(__name__) + + +def fetch_config_from_endpoint() -> dict: + """ + Fetch the configuration from the configuration endpoint. + """ + config_endpoint = os.getenv(CONFIG_ENDPOINT_ENV, None) + + if config_endpoint is None: + log.debug("Configuration endpoint not set. Skipping fetching configuration.") + return {} + + try: + parsed_url = parse.urlparse(config_endpoint) + connect = connector(config_endpoint) + with connect() as conn: + conn.request("GET", parsed_url.path or "/") + response = conn.getresponse() + if not (200 <= response.status < 300): + log.error("Failed to fetch configuration from endpoint, status code: %d", response.status) + return {} + + data = response.read().decode("utf-8") + log.debug("Fetched configuration from endpoint: %s", data) + return json.loads(data) + + except (json.JSONDecodeError, Exception): + log.error("Failed to fetch configuration from endpoint:", exc_info=True) + + return {} diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 06934519a77..a277e912829 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -1,5 +1,7 @@ import logging +import os import re +import subprocess import pytest @@ -27,6 +29,9 @@ from tests.utils import override_global_config +CONFIG_SERVER_PORT = "9596" + + @pytest.fixture def no_request_sampling(tracer): with override_env( @@ -148,3 +153,16 @@ def check_native_code_exception_in_each_python_aspect_test(request, caplog): # TODO(avara1986): iast tests throw a timeout in gitlab # list_metrics_logs = list(telemetry_writer._logs) # assert len(list_metrics_logs) == 0 + + +@pytest.fixture(scope="session") +def configuration_endpoint(): + current_dir = os.path.dirname(__file__) + cmd = [ + "python", + os.path.join(current_dir, "fixtures", "integration", "http_config_server.py"), + CONFIG_SERVER_PORT, + ] + process = subprocess.Popen(cmd, cwd=current_dir) + yield + process.kill() diff --git a/tests/appsec/iast/fixtures/integration/http_config_server.py b/tests/appsec/iast/fixtures/integration/http_config_server.py new file mode 100644 index 00000000000..1dc5793a402 --- /dev/null +++ b/tests/appsec/iast/fixtures/integration/http_config_server.py @@ -0,0 +1,42 @@ +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer +import json +import sys + + +IAST_ENABLED = {"iast_enabled": True} +IAST_DISABLED = {"iast_enabled": False} +IAST_UNSET = {} + + +class SimpleHandler(BaseHTTPRequestHandler): + def do_GET(self): + # Define the response payload (fixed JSON response) + path = self.path[:-1] if self.path.endswith("/") else self.path + if path.endswith("IAST_ENABLED"): + response = IAST_ENABLED + elif path.endswith("IAST_DISABLED"): + response = IAST_DISABLED + else: + response = IAST_UNSET + + # Send response status code + self.send_response(200) + + # Send headers + self.send_header("Content-type", "application/json") + self.end_headers() + + # Send the JSON response + self.wfile.write(json.dumps(response).encode("utf-8")) + + +# Start the server +if __name__ == "__main__": + try: + SERVER_PORT = int(sys.argv[-1]) + except Exception: + SERVER_PORT = 9090 + server_address = ("", SERVER_PORT) + httpd = HTTPServer(server_address, SimpleHandler) + httpd.serve_forever() diff --git a/tests/appsec/iast/test_env_var.py b/tests/appsec/iast/test_env_var.py index 77215ec34c2..a1fcac3cc54 100644 --- a/tests/appsec/iast/test_env_var.py +++ b/tests/appsec/iast/test_env_var.py @@ -4,6 +4,8 @@ import pytest +from .conftest import CONFIG_SERVER_PORT + def _run_python_file(*args, **kwargs): current_dir = os.path.dirname(__file__) @@ -50,6 +52,46 @@ def test_env_var_iast_unset(monkeypatch, capfd): assert "IAST enabled" not in captured.err +@pytest.mark.parametrize( + "env_vars", + [ + {"DD_IAST_ENABLED": "true"}, + {"DD_IAST_ENABLED": "true", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, + {"DD_IAST_ENABLED": "false", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED"}, + {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED"}, + ], +) +def test_env_var_iast_enabled_parametrized(capfd, configuration_endpoint, env_vars): + env = os.environ.copy() + for k, v in env_vars.items(): + env[k] = v + _run_python_file(env=env) + captured = capfd.readouterr() + assert "hi" in captured.out + assert "IAST enabled" in captured.err + + +@pytest.mark.parametrize( + "env_vars", + [ + {}, + {"DD_IAST_ENABLED": "false"}, + {"DD_IAST_ENABLED": "true", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED"}, + {"DD_IAST_ENABLED": "false", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, + {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED"}, + {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, + ], +) +def test_env_var_iast_disabled_parametrized(capfd, configuration_endpoint, env_vars): + env = os.environ.copy() + for k, v in env_vars.items(): + env[k] = v + _run_python_file(env=env) + captured = capfd.readouterr() + assert "hi" in captured.out + assert "IAST enabled" not in captured.err + + @pytest.mark.subprocess( env=dict(DD_IAST_ENABLED="False"), err=b"WARNING:root:IAST not enabled but native module is being loaded\n" ) diff --git a/tests/tracer/test_endpoint_config.py b/tests/tracer/test_endpoint_config.py new file mode 100644 index 00000000000..7e356827d9a --- /dev/null +++ b/tests/tracer/test_endpoint_config.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +from http.client import HTTPResponse +from io import BytesIO +from unittest import mock + +from ddtrace.internal.http import HTTPConnection +from ddtrace.settings.endpoint_config import fetch_config_from_endpoint +from tests.utils import override_env + + +def mock_getresponse_enabled(self): + response = mock.Mock(spec=HTTPResponse) + response.read.return_value = b'{"dd_iast_enabled": true}' + response.status = 200 + response.reason = "OK" + response.chunked = False + response.fp = BytesIO(response.read.return_value) + response.length = len(response.fp.getvalue()) + response.msg = {"Content-Length": response.length} + return response + + +def mock_getresponse_403(self): + response = mock.Mock(spec=HTTPResponse) + response.read.return_value = b'{"dd_iast_enabled": true}' + response.status = 403 + response.reason = "KO" + response.chunked = False + response.fp = BytesIO(response.read.return_value) + response.length = len(response.fp.getvalue()) + response.msg = {"Content-Length": response.length} + return response + + +def mock_getresponse_500(self): + response = mock.Mock(spec=HTTPResponse) + response.read.return_value = b'{"dd_iast_enabled": true}' + response.status = 500 + response.reason = "KO" + response.chunked = False + response.fp = BytesIO(response.read.return_value) + response.length = len(response.fp.getvalue()) + response.msg = {"Content-Length": response.length} + return response + + +def mock_getresponse_malformed(self): + response = mock.Mock(spec=HTTPResponse) + response.read.return_value = b"{" + response.status = 200 + response.reason = "OK" + response.chunked = False + response.fp = BytesIO(response.read.return_value) + response.length = len(response.fp.getvalue()) + response.msg = {"Content-Length": response.length} + return response + + +def mock_pass(self, *args, **kwargs): + pass + + +def test_unset_config_endpoint(caplog): + caplog.set_level(10) + with override_env({"DD_TRACE_DEBUG": "true"}): + assert fetch_config_from_endpoint() == {} + assert "Configuration endpoint not set. Skipping fetching configuration." in caplog.text + + +def test_set_config_endpoint_enabled(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object( + HTTPConnection, "connect", new=mock_pass + ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + HTTPConnection, "getresponse", new=mock_getresponse_enabled + ): + assert fetch_config_from_endpoint() == {"dd_iast_enabled": True} + assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text + assert "Failed to fetch configuration from endpoint" not in caplog.text + + +def test_set_config_endpoint_500(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( + HTTPConnection, "connect", new=mock_pass + ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + HTTPConnection, "getresponse", new=mock_getresponse_500 + ): + assert fetch_config_from_endpoint() == {} + assert "Failed to fetch configuration from endpoint, status code: 500" in caplog.text + + +def test_set_config_endpoint_403(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( + HTTPConnection, "connect", new=mock_pass + ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + HTTPConnection, "getresponse", new=mock_getresponse_403 + ): + assert fetch_config_from_endpoint() == {} + assert "Failed to fetch configuration from endpoint, status code: 403" in caplog.text + + +def test_set_config_endpoint_malformed(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( + HTTPConnection, "connect", new=mock_pass + ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + HTTPConnection, "getresponse", new=mock_getresponse_malformed + ): + assert fetch_config_from_endpoint() == {} + assert "Failed to fetch configuration from endpoint" in caplog.text + assert "Expecting property name enclosed in double quotes" in caplog.text + + +def test_set_config_endpoint_connection_refused(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}): + assert fetch_config_from_endpoint() == {} + assert "Failed to fetch configuration from endpoint" in caplog.text + assert any( + message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") + ), "None of the expected connection error log messages were found" From 79c0cf63f8b106bc7f6c86119c2808574a44f21f Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Tue, 5 Nov 2024 11:16:42 -0500 Subject: [PATCH 105/372] test: only auto-enable crashtracking in tests on linux (#11292) --- tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index d02964f9d0b..0533174236e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ import os from os.path import split from os.path import splitext +import platform import random import subprocess import sys @@ -116,7 +117,9 @@ def use_global_tracer(): @pytest.fixture def auto_enable_crashtracking(): - yield True + # Crashtracking is only supported on linux right now + # TODO: Default to `True` when Windows and Darwin are supported + yield platform.system() == "Linux" @pytest.fixture(autouse=True) From 19f4c58252311693a72aae42cfa9b19b215a1b50 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Tue, 5 Nov 2024 20:21:19 +0100 Subject: [PATCH 106/372] chore(iast): restore, update and improve the native import smoke_test (#11278) ## Description The previous smoke test for importing ddtrace IAST native module (both positive and negative routes) was removed on a previous PR. This restores it, adapting it to the new version (now the `_native` module doesn't produce an `ImportError` if `DD_IAST_ENABLED=0` but a warning log message). It will also run both positive and negative tests in a subprocess so the `.so` module doesn't have to be restarted or reloaded, which is not trivial for native modules. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- tests/smoke_test.py | 84 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/tests/smoke_test.py b/tests/smoke_test.py index a4fbe3d5a39..cbf5ebc8e61 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -1,8 +1,9 @@ +import copy +import os from platform import system +import subprocess import sys - -import ddtrace.appsec._ddwaf -import ddtrace.bootstrap.sitecustomize as module +import textwrap def mac_supported_iast_version(): @@ -14,13 +15,74 @@ def mac_supported_iast_version(): return True +# Code need to be run in a separate subprocess to reload since reloading .so files doesn't +# work like normal Python ones +test_native_load_code = """ +import os +import sys +import logging + +log_messages = [] + +class ListHandler(logging.Handler): + def emit(self, record): + log_messages.append(record.getMessage()) + +# the _native module will produce the "IAST not enabled..." message using the root logger (logger.warning) +# so we have to configure the capture handler on the root logger +root_logger = logging.getLogger() +root_logger.addHandler(ListHandler()) +root_logger.setLevel(logging.WARNING) + +try: + from ddtrace.appsec._iast._taint_tracking._native import ops + + if os.environ.get("DD_IAST_ENABLED") == "False": + assert any( + "IAST not enabled but native module is being loaded" in message + for message in log_messages + ) + else: + assert ops + assert len(log_messages) == 0 +except ImportError as e: + assert False, "Importing the native module failed, _native probably not compiled correctly: %s" % str(e) +""" + if __name__ == "__main__": + # ASM IAST smoke test + if sys.version_info >= (3, 6, 0) and system() != "Windows" and mac_supported_iast_version(): + print("Running native IAST module load test...") + test_code = textwrap.dedent(test_native_load_code) + cmd = [sys.executable, "-c", test_code] + orig_env = os.environ.copy() + copied_env = copy.deepcopy(orig_env) + + try: + print("Running native module load test with DD_IAST_ENABLED=False...") + copied_env["DD_IAST_ENABLED"] = "False" + result = subprocess.run(cmd, env=copied_env, capture_output=True, text=True) + assert result.returncode == 0, "Failed with DD_IAST_ENABLED=0: %s, %s" % (result.stdout, result.stderr) + + print("Running native module load test with DD_IAST_ENABLED=True...") + copied_env["DD_IAST_ENABLED"] = "True" + result = subprocess.run(cmd, env=copied_env, capture_output=True, text=True) + assert result.returncode == 0, "Failed with DD_IAST_ENABLED=1: %s, %s" % (result.stdout, result.stderr) + print("IAST module load tests completed successfully") + finally: + os.environ = orig_env + # ASM WAF smoke test - if system() == "Linux": - if not sys.maxsize > 2**32: - # 32-bit linux DDWAF not ready yet. - sys.exit(0) - - ddtrace.appsec._ddwaf.version() - assert ddtrace.appsec._ddwaf._DDWAF_LOADED - assert module.loaded + if system() != "Linux" or sys.maxsize > 2**32: + import ddtrace.appsec._ddwaf + import ddtrace.bootstrap.sitecustomize as module + + print("Running WAF module load test...") + # Proceed with the WAF module load test + ddtrace.appsec._ddwaf.version() + assert ddtrace.appsec._ddwaf._DDWAF_LOADED + assert module.loaded + print("WAF module load test completed successfully") + else: + # Skip the test for 32-bit Linux systems + print("Skipping test, 32-bit DDWAF not ready yet") From 99431da328e42dfdda0985d23927fbb5469641b7 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Tue, 5 Nov 2024 23:53:20 -0500 Subject: [PATCH 107/372] chore(llmobs): fix telemetry namespace for ragas (#11279) `ml_obs` is the correct namespace for llmobs that's been implemented in the telemetry backend update ragas telemetry metrics to use that namespace ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: lievan --- ddtrace/llmobs/_evaluators/ragas/faithfulness.py | 5 +++-- ddtrace/llmobs/_evaluators/runner.py | 3 ++- ddtrace/llmobs/_evaluators/sampler.py | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py index 9b70ff51651..f108619f30b 100644 --- a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py +++ b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py @@ -7,6 +7,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.telemetry import telemetry_writer +from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.internal.utils.version import parse_version from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX @@ -138,7 +139,7 @@ def __init__(self, llmobs_service): raise NotImplementedError("Failed to load dependencies for `ragas_faithfulness` evaluator") from e finally: telemetry_writer.add_count_metric( - namespace="llmobs", + namespace=TELEMETRY_APM_PRODUCT.LLMOBS, name="evaluators.init", value=1, tags=( @@ -164,7 +165,7 @@ def run_and_submit_evaluation(self, span_event: dict): return score_result_or_failure = self.evaluate(span_event) telemetry_writer.add_count_metric( - "llmobs", + TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore "evaluators.run", 1, tags=( diff --git a/ddtrace/llmobs/_evaluators/runner.py b/ddtrace/llmobs/_evaluators/runner.py index 8e535403c94..b6a6bc9e844 100644 --- a/ddtrace/llmobs/_evaluators/runner.py +++ b/ddtrace/llmobs/_evaluators/runner.py @@ -7,6 +7,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import PeriodicService from ddtrace.internal.telemetry import telemetry_writer +from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator from ddtrace.llmobs._evaluators.sampler import EvaluatorRunnerSampler @@ -55,7 +56,7 @@ def __init__(self, interval: float, llmobs_service=None, evaluators=None): raise e finally: telemetry_writer.add_count_metric( - namespace="llmobs", + namespace=TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore name="evaluators.init", value=1, tags=( diff --git a/ddtrace/llmobs/_evaluators/sampler.py b/ddtrace/llmobs/_evaluators/sampler.py index 0fbcad53536..82057c12176 100644 --- a/ddtrace/llmobs/_evaluators/sampler.py +++ b/ddtrace/llmobs/_evaluators/sampler.py @@ -8,6 +8,7 @@ from ddtrace import config from ddtrace.internal.logger import get_logger from ddtrace.internal.telemetry import telemetry_writer +from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.sampling_rule import SamplingRule @@ -66,7 +67,7 @@ def parsing_failed_because(msg, maybe_throw_this): TELEMETRY_LOG_LEVEL.ERROR, message="Evaluator sampling parsing failure because: {}".format(msg) ) telemetry_writer.add_count_metric( - namespace="llmobs", + namespace=TELEMETRY_APM_PRODUCT.LLMOBS, name="evaluators.error", value=1, tags=(("reason", "sampling_rule_parsing_failure"),), @@ -103,7 +104,7 @@ def parsing_failed_because(msg, maybe_throw_this): span_name = rule.get(EvaluatorRunnerSamplingRule.SPAN_NAME_KEY, SamplingRule.NO_RULE) evaluator_label = rule.get(EvaluatorRunnerSamplingRule.EVALUATOR_LABEL_KEY, SamplingRule.NO_RULE) telemetry_writer.add_distribution_metric( - "llmobs", + TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore "evaluators.rule_sample_rate", sample_rate, tags=(("evaluator_label", evaluator_label), ("span_name", span_name)), From 51fe5178e6c641bd59b1811e16f03db38b4b1e5d Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Wed, 6 Nov 2024 10:47:51 +0100 Subject: [PATCH 108/372] chore: update changelog for version 2.15.1 (#11254) - [x] update changelog for version 2.15.1 --------- Signed-off-by: Juanjo Alvarez --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e96260ee2bc..08895fb1035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.15.1 + + +### Bug Fixes + +- CI Visibility: + - Fixes a bug where `CODEOWNERS` would incorrectly fail to discard line-level trailing comments (eg: `@code/owner # my comment` would result in codeowners being parsed as `@code/owner`, `#`, `my`, and `comment`) + - Fixes unnecessary logging of an exception that would appear when trying to upload git metadata in an environment without functioning git (eg: missing `git` binary or `.git` directory) +- Code Security: + - Resolves an issue where importing the `google.cloud.storage.batch` module would fail raising an ImportError. +- Dynamic Instrumentation: + - Fixes an issue that prevented dynamic span tags probes from adding the requested tags to the requested span. +- LLM Observability: + - This fix resolves two issues with annotation contexts: + - annotations registered via annotation contexts were being applied globally. Annotations are now only applied to the current trace context and do not pollute to other threads & processes. + - annotations from nested annotation contexts were applied in a non-deterministic order. Annotations are now applied in the order they were registered. +- Profiling: + - fix a data race where span information associated with a thread was read and updated concurrently, leading to segfaults + - resolves an issue where endpoint profiling for stack v2 throws `TypeError` exception when it is given a `Span` with `None` span\_type. + +### Other Changes +- LLM Observability: + - Updates the merging behavior for tags when LLMObs.annotate is called multiple times on the same span so that the latest value for a tag key overrides the previous value. + + --- ## 2.15.0 From bd7cc029617ba47b89fc99b8714cb27432a556ed Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 6 Nov 2024 08:47:57 -0500 Subject: [PATCH 109/372] chore(llmobs): call periodic for runner when flushing (#11242) Ensure `periodic` for the runner is called for `LLMObs.flush` Since `LLMObs.flush` is used in our serverless wrapper (see [here](https://github.com/DataDog/datadog-lambda-python/blob/59e6e7c01019a80f3cb425c1c75ae58b404db6b5/datadog_lambda/wrapper.py#L389)), this will make sure all finished llm spans are evaluated for the serverless case This will also allow us to utilize `LLMObs.flush` for support cases for RAGAS. It's been a helpful work-around for when customers are missing traces, so it will likely be helpful for debugging missing ragas evals as well. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: lievan --- ddtrace/llmobs/_llmobs.py | 6 ++++++ tests/llmobs/conftest.py | 33 +++++++++++++++++++++-------- tests/llmobs/test_llmobs_service.py | 27 +++++++++++++++-------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 15fbc23050c..032eafecd4e 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -327,6 +327,12 @@ def flush(cls) -> None: if cls.enabled is False: log.warning("flushing when LLMObs is disabled. No spans or evaluation metrics will be sent.") return + + try: + cls._instance._evaluator_runner.periodic() + except Exception: + log.warning("Failed to run evaluator runner.", exc_info=True) + try: cls._instance._llmobs_span_writer.periodic() cls._instance._llmobs_eval_metric_writer.periodic() diff --git a/tests/llmobs/conftest.py b/tests/llmobs/conftest.py index 90f7b8a8b97..0b0ce8b7964 100644 --- a/tests/llmobs/conftest.py +++ b/tests/llmobs/conftest.py @@ -60,6 +60,16 @@ def mock_llmobs_eval_metric_writer(): patcher.stop() +@pytest.fixture +def mock_llmobs_evaluator_runner(): + patcher = mock.patch("ddtrace.llmobs._llmobs.EvaluatorRunner") + LLMObsEvalRunner = patcher.start() + m = mock.MagicMock() + LLMObsEvalRunner.return_value = m + yield m + patcher.stop() + + @pytest.fixture def mock_llmobs_submit_evaluation(): patcher = mock.patch("ddtrace.llmobs._llmobs.LLMObs.submit_evaluation") @@ -129,7 +139,9 @@ def default_global_config(): @pytest.fixture -def LLMObs(mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, ddtrace_global_config): +def LLMObs( + mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, mock_llmobs_evaluator_runner, ddtrace_global_config +): global_config = default_global_config() global_config.update(ddtrace_global_config) with override_global_config(global_config): @@ -140,7 +152,12 @@ def LLMObs(mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, ddtrace_glob @pytest.fixture -def AgentlessLLMObs(mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_writer, ddtrace_global_config): +def AgentlessLLMObs( + mock_llmobs_span_agentless_writer, + mock_llmobs_eval_metric_writer, + mock_llmobs_evaluator_runner, + ddtrace_global_config, +): global_config = default_global_config() global_config.update(ddtrace_global_config) global_config.update(dict(_llmobs_agentless_enabled=True)) @@ -152,13 +169,11 @@ def AgentlessLLMObs(mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_w @pytest.fixture -def mock_llmobs_evaluator_runner(): - patcher = mock.patch("ddtrace.llmobs._evaluators.runner.EvaluatorRunner.enqueue") - LLMObsMockEvaluatorRunner = patcher.start() - m = mock.MagicMock() - LLMObsMockEvaluatorRunner.return_value = m - yield m - patcher.stop() +def disabled_llmobs(): + prev = llmobs_service.enabled + llmobs_service.enabled = False + yield + llmobs_service.enabled = prev @pytest.fixture diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 303dcb78865..ba43ecad56a 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1355,37 +1355,46 @@ def test_submit_evaluation_with_numerical_metric_enqueues_writer_with_score_metr def test_flush_calls_periodic_agentless( - AgentlessLLMObs, mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_writer + AgentlessLLMObs, mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_writer, mock_llmobs_evaluator_runner ): AgentlessLLMObs.flush() mock_llmobs_span_agentless_writer.periodic.assert_called_once() mock_llmobs_eval_metric_writer.periodic.assert_called_once() + mock_llmobs_evaluator_runner.periodic.assert_called_once() -def test_flush_does_not_call_period_when_llmobs_is_disabled( - LLMObs, mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, mock_logs +def test_flush_does_not_call_periodic_when_llmobs_is_disabled( + LLMObs, + mock_llmobs_span_writer, + mock_llmobs_eval_metric_writer, + mock_llmobs_evaluator_runner, + mock_logs, + disabled_llmobs, ): - LLMObs.disable() LLMObs.flush() mock_llmobs_span_writer.periodic.assert_not_called() mock_llmobs_eval_metric_writer.periodic.assert_not_called() + mock_llmobs_evaluator_runner.periodic.assert_not_called() mock_logs.warning.assert_has_calls( [mock.call("flushing when LLMObs is disabled. No spans or evaluation metrics will be sent.")] ) - LLMObs.enable() -def test_flush_does_not_call_period_when_llmobs_is_disabled_agentless( - AgentlessLLMObs, mock_llmobs_span_agentless_writer, mock_llmobs_eval_metric_writer, mock_logs +def test_flush_does_not_call_periodic_when_llmobs_is_disabled_agentless( + AgentlessLLMObs, + mock_llmobs_span_agentless_writer, + mock_llmobs_eval_metric_writer, + mock_llmobs_evaluator_runner, + mock_logs, + disabled_llmobs, ): - AgentlessLLMObs.disable() AgentlessLLMObs.flush() mock_llmobs_span_agentless_writer.periodic.assert_not_called() mock_llmobs_eval_metric_writer.periodic.assert_not_called() + mock_llmobs_evaluator_runner.periodic.assert_not_called() mock_logs.warning.assert_has_calls( [mock.call("flushing when LLMObs is disabled. No spans or evaluation metrics will be sent.")] ) - AgentlessLLMObs.enable() def test_inject_distributed_headers_llmobs_disabled_does_nothing(LLMObs, mock_logs): From 3cfe7dd69dc7fe59992593484d6292ef020f4485 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Wed, 6 Nov 2024 17:05:10 +0100 Subject: [PATCH 110/372] fix(iast): add google.auth and googlecloudsdk to the IAST denylist (#11305) ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_iast/_ast/ast_patching.py | 3 ++- releasenotes/notes/google-sdk-denylist-0619f1734507019a.yaml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/google-sdk-denylist-0619f1734507019a.yaml diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 22494c7da52..257b32923f6 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -300,7 +300,8 @@ "uvicorn.", "anyio.", "httpcore.", - "google.auth.crypt.", + "google.auth.", + "googlecloudsdk.", ) diff --git a/releasenotes/notes/google-sdk-denylist-0619f1734507019a.yaml b/releasenotes/notes/google-sdk-denylist-0619f1734507019a.yaml new file mode 100644 index 00000000000..8626c3caee5 --- /dev/null +++ b/releasenotes/notes/google-sdk-denylist-0619f1734507019a.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Add googlecloudsdk and google auth to the Code Security deny list. From 9facd394dce5df33b2f1ce779774bfbca99f1a70 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:26:31 +0000 Subject: [PATCH 111/372] chore: update kombu latest version to 5.4.2 (#11273) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/1030725.txt | 8 ++++---- .riot/requirements/12aafe0.txt | 17 +++++++++-------- .riot/requirements/1959ed5.txt | 11 ++++++----- .riot/requirements/1dd7f62.txt | 12 ++++++------ .riot/requirements/1e82f55.txt | 13 +++++++------ .riot/requirements/1fcb05f.txt | 8 ++++---- .riot/requirements/67c0ba5.txt | 11 ++++++----- .riot/requirements/7c88ce5.txt | 14 +++++++------- .riot/requirements/c285110.txt | 10 +++++----- .riot/requirements/ef10d26.txt | 8 ++++---- .riot/requirements/f81bf39.txt | 14 +++++++------- 11 files changed, 65 insertions(+), 61 deletions(-) diff --git a/.riot/requirements/1030725.txt b/.riot/requirements/1030725.txt index 7432b214662..6756c2a3758 100644 --- a/.riot/requirements/1030725.txt +++ b/.riot/requirements/1030725.txt @@ -6,7 +6,7 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 hypothesis==6.45.0 iniconfig==2.0.0 kombu==5.2.4 @@ -14,9 +14,9 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 vine==5.1.0 diff --git a/.riot/requirements/12aafe0.txt b/.riot/requirements/12aafe0.txt index cb433455fb5..20515a2467b 100644 --- a/.riot/requirements/12aafe0.txt +++ b/.riot/requirements/12aafe0.txt @@ -6,22 +6,23 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -kombu==5.4.0 +kombu==5.4.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 +tzdata==2024.2 vine==5.1.0 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/1959ed5.txt b/.riot/requirements/1959ed5.txt index 949a87f04ff..fc7009178cc 100644 --- a/.riot/requirements/1959ed5.txt +++ b/.riot/requirements/1959ed5.txt @@ -6,17 +6,18 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 hypothesis==6.45.0 iniconfig==2.0.0 -kombu==5.4.0 +kombu==5.4.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 +tzdata==2024.2 vine==5.1.0 diff --git a/.riot/requirements/1dd7f62.txt b/.riot/requirements/1dd7f62.txt index c60e212f86b..fe09a734e9e 100644 --- a/.riot/requirements/1dd7f62.txt +++ b/.riot/requirements/1dd7f62.txt @@ -10,20 +10,20 @@ backports-zoneinfo[tzdata]==0.2.1 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -kombu==5.4.0 +kombu==5.4.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 typing-extensions==4.12.2 -tzdata==2024.1 +tzdata==2024.2 vine==5.1.0 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/1e82f55.txt b/.riot/requirements/1e82f55.txt index e52c332f04c..2c1335e4583 100644 --- a/.riot/requirements/1e82f55.txt +++ b/.riot/requirements/1e82f55.txt @@ -6,19 +6,20 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 -kombu==5.4.0 +kombu==5.4.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 +tzdata==2024.2 vine==5.1.0 diff --git a/.riot/requirements/1fcb05f.txt b/.riot/requirements/1fcb05f.txt index 5dd7f453eb1..2cb4ebee883 100644 --- a/.riot/requirements/1fcb05f.txt +++ b/.riot/requirements/1fcb05f.txt @@ -9,18 +9,18 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 kombu==4.6.11 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 vine==1.3.0 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/67c0ba5.txt b/.riot/requirements/67c0ba5.txt index 5335ba570dc..932f34afe30 100644 --- a/.riot/requirements/67c0ba5.txt +++ b/.riot/requirements/67c0ba5.txt @@ -6,17 +6,18 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 hypothesis==6.45.0 iniconfig==2.0.0 -kombu==5.4.0 +kombu==5.4.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 +tzdata==2024.2 vine==5.1.0 diff --git a/.riot/requirements/7c88ce5.txt b/.riot/requirements/7c88ce5.txt index d50e8458016..03604f27cb4 100644 --- a/.riot/requirements/7c88ce5.txt +++ b/.riot/requirements/7c88ce5.txt @@ -6,21 +6,21 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 kombu==5.0.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 vine==5.1.0 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/c285110.txt b/.riot/requirements/c285110.txt index 141cf0738b5..2dfe3e3d987 100644 --- a/.riot/requirements/c285110.txt +++ b/.riot/requirements/c285110.txt @@ -6,7 +6,7 @@ # amqp==5.2.0 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 @@ -15,10 +15,10 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 vine==5.1.0 diff --git a/.riot/requirements/ef10d26.txt b/.riot/requirements/ef10d26.txt index 25b8e17c775..a81a7ec4285 100644 --- a/.riot/requirements/ef10d26.txt +++ b/.riot/requirements/ef10d26.txt @@ -9,18 +9,18 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 kombu==5.0.2 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 vine==5.1.0 -zipp==3.20.1 +zipp==3.20.2 diff --git a/.riot/requirements/f81bf39.txt b/.riot/requirements/f81bf39.txt index 46474859d89..047596b2791 100644 --- a/.riot/requirements/f81bf39.txt +++ b/.riot/requirements/f81bf39.txt @@ -6,21 +6,21 @@ # amqp==2.6.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 kombu==4.6.11 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 vine==1.3.0 -zipp==3.20.1 +zipp==3.20.2 From 0923b515f872fad09f1f08c5dd8a20bc309d1d1d Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 6 Nov 2024 13:57:40 -0500 Subject: [PATCH 112/372] fix(profiling): stack v2 tracks threads in gunicorn workers (#11300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See the following screenshot for cpu profile before and after this change for a service using gunicorn. Screenshot 2024-11-06 at 1 55 56 PM ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/profiling/collector/threading.py | 8 ++- ...iling-gunicorn-track-06a211fc1e2643e1.yaml | 5 ++ tests/profiling/collector/pprof_utils.py | 66 +++++++++++-------- tests/profiling/gunicorn-app.py | 21 +++++- tests/profiling_v2/collector/test_stack.py | 12 ++-- tests/profiling_v2/test_gunicorn.py | 58 +++++++++++++--- 6 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 releasenotes/notes/profiling-gunicorn-track-06a211fc1e2643e1.yaml diff --git a/ddtrace/profiling/collector/threading.py b/ddtrace/profiling/collector/threading.py index 84a03953442..86daee689f6 100644 --- a/ddtrace/profiling/collector/threading.py +++ b/ddtrace/profiling/collector/threading.py @@ -62,4 +62,10 @@ def thread_bootstrap_inner(self, *args, **kwargs): # Instrument any living threads for thread_id, thread in threading._active.items(): - stack_v2.register_thread(thread.ident, thread.native_id, thread.name) + # DEV: calling _set_native_id will register the thread with stack_v2 + # as we've already patched it. + # Calling _set_native_id was necessary to ensure that the native_id + # was set on the thread running in gunicorn workers. They need to be + # updated with correct native_id so that the thread can be tracked + # correctly in the echion stack_v2. + thread._set_native_id() diff --git a/releasenotes/notes/profiling-gunicorn-track-06a211fc1e2643e1.yaml b/releasenotes/notes/profiling-gunicorn-track-06a211fc1e2643e1.yaml new file mode 100644 index 00000000000..d14a780b7c0 --- /dev/null +++ b/releasenotes/notes/profiling-gunicorn-track-06a211fc1e2643e1.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: fixes an issue where cpu-time was not profiled for services using + gunicorn, when ``DD_PROFILING_STACK_V2_ENABLED` was set. diff --git a/tests/profiling/collector/pprof_utils.py b/tests/profiling/collector/pprof_utils.py index 5d5c5aab22f..59f2795b6d6 100644 --- a/tests/profiling/collector/pprof_utils.py +++ b/tests/profiling/collector/pprof_utils.py @@ -192,7 +192,7 @@ def assert_lock_events( assert_lock_events_of_type(profile, expected_release_events, LockEventType.RELEASE) -def assert_str_label(string_table, sample, key: str, expected_value: Optional[str]): +def assert_str_label(string_table: Dict[int, str], sample, key: str, expected_value: Optional[str]): if expected_value: label = get_label_with_key(string_table, sample, key) # We use fullmatch to ensure that the whole string matches the expected value @@ -203,25 +203,25 @@ def assert_str_label(string_table, sample, key: str, expected_value: Optional[st ) -def assert_num_label(string_table, sample, key: str, expected_value: Optional[int]): +def assert_num_label(string_table: Dict[int, str], sample, key: str, expected_value: Optional[int]): if expected_value: label = get_label_with_key(string_table, sample, key) assert label.num == expected_value, "Expected {} got {} for label {}".format(expected_value, label.num, key) -def assert_base_event(profile, sample: pprof_pb2.Sample, expected_event: EventBaseClass): - assert_num_label(profile.string_table, sample, "span id", expected_event.span_id) - assert_num_label(profile.string_table, sample, "local root span id", expected_event.local_root_span_id) - assert_str_label(profile.string_table, sample, "trace type", expected_event.trace_type) - assert_str_label(profile.string_table, sample, "trace endpoint", expected_event.trace_endpoint) - assert_num_label(profile.string_table, sample, "thread id", expected_event.thread_id) - assert_str_label(profile.string_table, sample, "thread name", expected_event.thread_name) - assert_str_label(profile.string_table, sample, "class name", expected_event.class_name) - assert_num_label(profile.string_table, sample, "task id", expected_event.task_id) - assert_str_label(profile.string_table, sample, "task name", expected_event.task_name) +def assert_base_event(string_table: Dict[int, str], sample: pprof_pb2.Sample, expected_event: EventBaseClass): + assert_num_label(string_table, sample, "span id", expected_event.span_id) + assert_num_label(string_table, sample, "local root span id", expected_event.local_root_span_id) + assert_str_label(string_table, sample, "trace type", expected_event.trace_type) + assert_str_label(string_table, sample, "trace endpoint", expected_event.trace_endpoint) + assert_num_label(string_table, sample, "thread id", expected_event.thread_id) + assert_str_label(string_table, sample, "thread name", expected_event.thread_name) + assert_str_label(string_table, sample, "class name", expected_event.class_name) + assert_num_label(string_table, sample, "task id", expected_event.task_id) + assert_str_label(string_table, sample, "task name", expected_event.task_name) -def assert_lock_event(profile, sample: pprof_pb2.Sample, expected_event: LockEvent): +def assert_lock_event(profile: pprof_pb2.Profile, sample: pprof_pb2.Sample, expected_event: LockEvent): # Check that the sample has label "lock name" with value # filename:self.lock_linenos.create:lock_name lock_name_label = get_label_with_key(profile.string_table, sample, "lock name") @@ -254,7 +254,7 @@ def assert_lock_event(profile, sample: pprof_pb2.Sample, expected_event: LockEve expected_event.linenos.release, line.line ) - assert_base_event(profile, sample, expected_event) + assert_base_event(profile.string_table, sample, expected_event) def assert_sample_has_locations(profile, sample, expected_locations: Optional[List[StackLocation]]): @@ -277,34 +277,42 @@ def assert_sample_has_locations(profile, sample, expected_locations: Optional[Li filename = os.path.basename(profile.string_table[function.filename]) line_no = line.line sample_loc_strs.append(f"{filename}:{function_name}:{line_no}") - if ( - function_name.endswith(expected_locations[expected_locations_idx].function_name) - and filename == expected_locations[expected_locations_idx].filename - and line_no == expected_locations[expected_locations_idx].line_no - ): - expected_locations_idx += 1 - if expected_locations_idx == len(expected_locations): - checked = True - break + + if expected_locations_idx < len(expected_locations): + if ( + function_name.endswith(expected_locations[expected_locations_idx].function_name) + and filename == expected_locations[expected_locations_idx].filename + and line_no == expected_locations[expected_locations_idx].line_no + ): + expected_locations_idx += 1 + if expected_locations_idx == len(expected_locations): + checked = True + + for loc in sample_loc_strs: + if DEBUG_TEST: + print(loc) assert checked, "Expected locations {} not found in sample locations: {}".format( expected_locations, sample_loc_strs ) -def assert_stack_event(profile, sample: pprof_pb2.Sample, expected_event: StackEvent): +def assert_stack_event(profile: pprof_pb2.Profile, sample: pprof_pb2.Sample, expected_event: StackEvent): # Check that the sample has label "exception type" with value - assert_str_label(profile.string_table, sample, "exception type", expected_event.exception_type) assert_sample_has_locations(profile, sample, expected_event.locations) - assert_base_event(profile, sample, expected_event) + assert_base_event(profile.string_table, sample, expected_event) -def assert_has_samples(profile, expected_samples): +def assert_profile_has_sample( + profile: pprof_pb2.Profile, + samples: List[pprof_pb2.Sample], + expected_sample: StackEvent, +): found = False - for sample in profile.sample: + for sample in samples: try: - assert_stack_event(profile, sample, expected_samples) + assert_stack_event(profile, sample, expected_sample) found = True break except AssertionError as e: diff --git a/tests/profiling/gunicorn-app.py b/tests/profiling/gunicorn-app.py index 9a05b59f465..b76e1babdff 100644 --- a/tests/profiling/gunicorn-app.py +++ b/tests/profiling/gunicorn-app.py @@ -1,2 +1,19 @@ -def app(): - pass +import os +import threading + + +def fib(n): + if n <= 1: + return n + return fib(n - 1) + fib(n - 2) + + +def app(environ, start_response): + response_body = "fib(35) is %d at pid %d tid %d" % (fib(35), os.getpid(), threading.get_ident()) + + response_body = response_body.encode("utf-8") + + status = "200 OK" if response_body else "404 Not Found" + headers = [("Content-type", "text/plain")] + start_response(status, headers) + return [response_body] diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index f5c99b2c63d..d7e59a9266c 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -69,7 +69,7 @@ def foo(): ], ) - pprof_utils.assert_has_samples(profile, expected_sample) + pprof_utils.assert_profile_has_sample(profile, samples=samples, expected_sample=expected_sample) @pytest.mark.parametrize("stack_v2_enabled", [True, False]) @@ -471,9 +471,10 @@ def sleep_instance(self): samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") assert len(samples) > 0 - pprof_utils.assert_has_samples( + pprof_utils.assert_profile_has_sample( profile, - pprof_utils.StackEvent( + samples=samples, + expected_sample=pprof_utils.StackEvent( thread_id=_thread.get_ident(), thread_name="MainThread", class_name="SomeClass" if not stack_v2_enabled else None, @@ -529,9 +530,10 @@ def sleep_instance(foobar, self): samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") assert len(samples) > 0 - pprof_utils.assert_has_samples( + pprof_utils.assert_profile_has_sample( profile, - pprof_utils.StackEvent( + samples=samples, + expected_sample=pprof_utils.StackEvent( thread_id=_thread.get_ident(), thread_name="MainThread", # stack v1 relied on using cls and self to figure out class name diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index a56dc1b8b07..79c33777a59 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -4,12 +4,23 @@ import subprocess import sys import time +import urllib.request import pytest from tests.profiling.collector import pprof_utils +# DEV: gunicorn tests are hard to debug, so keeping these print statements for +# future debugging +DEBUG_PRINT = False + + +def debug_print(*args): + if DEBUG_PRINT: + print(*args) + + # gunicorn is not available on Windows if sys.platform == "win32": pytestmark = pytest.mark.skip @@ -28,8 +39,7 @@ def _run_gunicorn(*args): @pytest.fixture def gunicorn(monkeypatch): - # Do not ignore profiler so we have samples in the output pprof - monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "0") + monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "1") monkeypatch.setenv("DD_PROFILING_ENABLED", "1") # This was needed for the gunicorn process to start and print worker startup # messages. Without this, the test can't find the worker PIDs. @@ -48,19 +58,51 @@ def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): filename = str(tmp_path / "gunicorn.pprof") monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) - proc = gunicorn("-w", "3", *args) + # DEV: We only start 1 worker to simplify the test + proc = gunicorn("-w", "1", *args) + # Wait for the workers to start time.sleep(3) - proc.terminate() + + try: + with urllib.request.urlopen("http://127.0.0.1:7643") as f: + status_code = f.getcode() + assert status_code == 200, status_code + response = f.read().decode() + debug_print(response) + + except Exception as e: + pytest.fail("Failed to make request to gunicorn server %s" % e) + finally: + # Need to terminate the process to get the output and release the port + proc.terminate() + output = proc.stdout.read().decode() worker_pids = _get_worker_pids(output) - assert len(worker_pids) == 3, output + for line in output.splitlines(): + debug_print(line) + + assert len(worker_pids) == 1, output assert proc.wait() == 0, output assert "module 'threading' has no attribute '_active'" not in output, output - profile = pprof_utils.parse_profile("%s.%d" % (filename, proc.pid)) - samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") - assert len(samples) > 0 + for pid in worker_pids: + debug_print("Reading pprof file with prefix %s.%d" % (filename, pid)) + profile = pprof_utils.parse_profile("%s.%d" % (filename, pid)) + # This returns a list of samples that have non-zero cpu-time + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + assert len(samples) > 0 + + pprof_utils.assert_profile_has_sample( + profile, + samples=samples, + expected_sample=pprof_utils.StackEvent( + locations=[ + pprof_utils.StackLocation(function_name="fib", filename="gunicorn-app.py", line_no=8), + pprof_utils.StackLocation(function_name="fib", filename="gunicorn-app.py", line_no=8), + ] + ), + ) def test_gunicorn(gunicorn, tmp_path, monkeypatch): From 8ed195073595088087fbb77c1401b2505a865a74 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 6 Nov 2024 21:05:31 +0000 Subject: [PATCH 113/372] ci: add stack v2 profiling to Django profiling (#11291) We enable the stack v2 profiler in the Django overhead profiling for collecting profiles using the Datadog profiler, while also profiling any additional overhead introduced by the profiler itself. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/django-overhead-profile.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index 01a1626e0d8..3d5add5d559 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -16,6 +16,9 @@ jobs: env: PREFIX: ${{ github.workspace }}/prefix DD_CODE_ORIGIN_FOR_SPANS_ENABLED: "1" + DD_PROFILING_ENABLED: "1" + DD_PROFILING_STACK_V2_ENABLED: "1" + DD_PROFILING_OUTPUT_PPROF: ${{ github.workspace }}/prefix/artifacts/ddtrace_profile.pprof defaults: run: working-directory: ddtrace From 9f6776db3eb7a83c0b00033d8ce456f5041b3eb1 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 7 Nov 2024 10:25:45 +0000 Subject: [PATCH 114/372] chore(di): fix top of stack in function snapshots (#11239) We fix a regression introduced with #11153 whereby the top of the stack might have incorrect line number information ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_signal/snapshot.py | 11 +++++++++++ tests/debugging/test_debugger.py | 1 + 2 files changed, 12 insertions(+) diff --git a/ddtrace/debugging/_signal/snapshot.py b/ddtrace/debugging/_signal/snapshot.py index 54b23830be5..9f42921a7a3 100644 --- a/ddtrace/debugging/_signal/snapshot.py +++ b/ddtrace/debugging/_signal/snapshot.py @@ -137,6 +137,17 @@ def exit(self, retval, exc_info, duration, scope) -> None: self.duration = duration self.return_capture = self._do(retval, exc_info, scope) + # Fix the line number of the top frame. This might have been mangled by + # the instrumented exception handling of function probes. + assert self._stack is not None # nosec B101 + tb = exc_info[2] + while tb is not None: + frame = tb.tb_frame + if frame == self.frame: + self._stack[0]["lineNumber"] = tb.tb_lineno + break + tb = tb.tb_next + def line(self, scope) -> None: self.line_capture = self._do(_NOTSET, sys.exc_info(), scope) diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index a59824741b6..ad2771cfb71 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -207,6 +207,7 @@ def test_debugger_function_probe_on_function_with_exception(): snapshot_data = snapshot["debugger"]["snapshot"] assert snapshot_data["stack"][0]["fileName"].endswith("stuff.py") assert snapshot_data["stack"][0]["function"] == "throwexcstuff" + assert snapshot_data["stack"][0]["lineNumber"] == 110 entry_capture = snapshot_data["captures"]["entry"] assert entry_capture["arguments"] == {} From c2eb5eea5644135c4aff4d9e9add232531214b35 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 7 Nov 2024 10:32:24 +0000 Subject: [PATCH 115/372] refactor: use telemetry in product protocol (#11095) We ensure that every service that is enabled/disabled via the new product plugin interface sends telemetry event update automatically. It won't be necessary for each product using the interface to manually call into the telemetry service. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 4 ---- ddtrace/internal/products.py | 3 +++ ddtrace/internal/telemetry/constants.py | 2 +- ddtrace/internal/telemetry/writer.py | 14 +++++++------- ddtrace/internal/utils/version.py | 3 +++ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 4c2369d6f42..6d0edf3a224 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -65,8 +65,6 @@ from ddtrace.internal.rate_limiter import RateLimitExceeded from ddtrace.internal.remoteconfig.worker import remoteconfig_poller from ddtrace.internal.service import Service -from ddtrace.internal.telemetry import telemetry_writer -from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.wrapping.context import WrappingContext @@ -314,7 +312,6 @@ def enable(cls) -> None: debugger.start() register_post_run_module_hook(cls._on_run_module) - telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.DYNAMIC_INSTRUMENTATION, True) log.debug("%s enabled", cls.__name__) @@ -343,7 +340,6 @@ def disable(cls, join: bool = True) -> None: metrics.disable() di_config.enabled = False - telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.DYNAMIC_INSTRUMENTATION, False) log.debug("%s disabled", cls.__name__) diff --git a/ddtrace/internal/products.py b/ddtrace/internal/products.py index c49ac9e0339..3a8ff05b4a1 100644 --- a/ddtrace/internal/products.py +++ b/ddtrace/internal/products.py @@ -6,6 +6,7 @@ from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger +from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.uwsgi import check_uwsgi from ddtrace.internal.uwsgi import uWSGIConfigError from ddtrace.internal.uwsgi import uWSGIMasterProcess @@ -119,6 +120,7 @@ def start_products(self) -> None: try: product.start() log.debug("Started product '%s'", name) + telemetry_writer.product_activated(name, True) except Exception: log.exception("Failed to start product '%s'", name) failed.add(name) @@ -148,6 +150,7 @@ def stop_products(self, join: bool = False) -> None: try: product.stop(join=join) log.debug("Stopped product '%s'", name) + telemetry_writer.product_activated(name, False) except Exception: log.exception("Failed to stop product '%s'", name) diff --git a/ddtrace/internal/telemetry/constants.py b/ddtrace/internal/telemetry/constants.py index e6ac6ca6359..3298fdd7616 100644 --- a/ddtrace/internal/telemetry/constants.py +++ b/ddtrace/internal/telemetry/constants.py @@ -16,7 +16,7 @@ class TELEMETRY_LOG_LEVEL(Enum): ERROR = "ERROR" -class TELEMETRY_APM_PRODUCT(Enum): +class TELEMETRY_APM_PRODUCT(str, Enum): LLMOBS = "mlobs" DYNAMIC_INSTRUMENTATION = "dynamic_instrumentation" PROFILER = "profiler" diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 326e350e7b7..c6232d0064c 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -27,7 +27,7 @@ from ..service import ServiceStatus from ..utils.formats import asbool from ..utils.time import StopWatch -from ..utils.version import _pep440_to_semver +from ..utils.version import version as tracer_version from . import modules from .constants import TELEMETRY_APM_PRODUCT from .constants import TELEMETRY_LOG_LEVEL # noqa:F401 @@ -93,7 +93,7 @@ def __init__(self, agentless): self._headers = { "Content-Type": "application/json", "DD-Client-Library-Language": "python", - "DD-Client-Library-Version": _pep440_to_semver(), + "DD-Client-Library-Version": tracer_version, } if agentless and _TelemetryConfig.API_KEY: @@ -331,7 +331,7 @@ def _app_started(self, register_app_shutdown=True): self.started = True products = { - product: {"version": _pep440_to_semver(), "enabled": status} + product: {"version": tracer_version, "enabled": status} for product, status in self._product_enablement.items() } @@ -438,7 +438,7 @@ def _app_product_change(self): payload = { "products": { - product: {"version": _pep440_to_semver(), "enabled": status} + product: {"version": tracer_version, "enabled": status} for product, status in self._product_enablement.items() } } @@ -446,13 +446,13 @@ def _app_product_change(self): self._send_product_change_updates = False def product_activated(self, product, enabled): - # type: (TELEMETRY_APM_PRODUCT, bool) -> None + # type: (str, bool) -> None """Updates the product enablement dict""" - if self._product_enablement[product.value] == enabled: + if self._product_enablement.get(product, False) is enabled: return - self._product_enablement[product.value] = enabled + self._product_enablement[product] = enabled # If the app hasn't started, the product status will be included in the app_started event's payload if self.started: diff --git a/ddtrace/internal/utils/version.py b/ddtrace/internal/utils/version.py index 6a05b2b3881..b3ab75d5ef4 100644 --- a/ddtrace/internal/utils/version.py +++ b/ddtrace/internal/utils/version.py @@ -77,3 +77,6 @@ def _pep440_to_semver(version=None): elif ".dev" in tracer_version: tracer_version = tracer_version.replace(".dev", "-dev", 1) return tracer_version + + +version = _pep440_to_semver() From 11417542a2bfef029c7177017d957235b2323d12 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:19:53 +0100 Subject: [PATCH 116/372] chore(asm): improve logs in asm_request_context (#11307) Improve logs in asm_request_context - Use log strings with context::function::info - Add tool to get the previous frame info to know where the function was called from (disabled for python 3.7) - Add unit test - Upgrade a few logs from debug to warning ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_asm_request_context.py | 40 ++++++++++--------- ddtrace/appsec/_utils.py | 9 +++++ .../appsec/appsec/test_asm_request_context.py | 12 ++++++ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 6a543ac3f56..e3a87672e05 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -16,6 +16,7 @@ from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._utils import add_context_log from ddtrace.appsec._utils import get_triggers from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException @@ -66,7 +67,8 @@ def __init__(self, span: Optional[Span] = None): # add several layers of fallbacks to get a span, but normal span should be the first or the second one context_span = span or core.get_span() or tracer.current_span() if context_span is None: - log.debug("ASM context created without an available span") + info = add_context_log(log, "appsec.asm_context.warning::ASM_Environment::no_span") + log.warning(info) context_span = tracer.trace("asm.context") self.span: Span = context_span self.waf_info: Optional[Callable[[], "DDWaf_info"]] = None @@ -158,7 +160,8 @@ def set_blocked(blocked: Dict[str, Any]) -> None: blocked = blocked.copy() env = _get_asm_context() if env is None: - log.debug("setting blocked with no active asm context") + info = add_context_log(log, "appsec.asm_context.warning::set_blocked::no_active_context") + log.warning(info) return _ctype_from_headers(blocked, get_headers()) env.blocked = blocked @@ -219,12 +222,12 @@ def finalize_asm_env(env: ASM_Environment) -> None: try: if info.errors: env.span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors) - log.debug("Error in ASM In-App WAF: %s", info.errors) + log.debug("appsec.asm_context.debug::finalize_asm_env::waf_errors::%s", info.errors) env.span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version) env.span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded) env.span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed) except Exception: - log.debug("Error executing ASM In-App WAF metrics report: %s", exc_info=True) + log.debug("appsec.asm_context.debug::finalize_asm_env::exception::%s", exc_info=True) core.discard_local_item(_ASM_CONTEXT) @@ -232,7 +235,8 @@ def finalize_asm_env(env: ASM_Environment) -> None: def set_value(category: str, address: str, value: Any) -> None: env = _get_asm_context() if env is None: - log.debug("setting %s address %s with no active asm context", category, address) + info = add_context_log(log, f"appsec.asm_context.debug::set_value::no_active_context::{category}::{address}") + log.debug(info) return asm_context_attr = getattr(env, category, None) if asm_context_attr is not None: @@ -268,7 +272,8 @@ def set_waf_address(address: str, value: Any) -> None: def get_value(category: str, address: str, default: Any = None) -> Any: env = _get_asm_context() if env is None: - log.debug("getting %s address %s with no active asm context", category, address) + info = add_context_log(log, f"appsec.asm_context.debug::get_value::no_active_context::{category}::{address}") + log.debug(info) return default asm_context_attr = getattr(env, category, None) if asm_context_attr is not None: @@ -280,14 +285,6 @@ def get_waf_address(address: str, default: Any = None) -> Any: return get_value(_WAF_ADDRESSES, address, default=default) -def get_waf_addresses() -> Dict[str, Any]: - env = _get_asm_context() - if env is None: - log.debug("getting WAF addresses with no active asm context") - return {} - return env.waf_addresses - - def add_context_callback(function, global_callback: bool = False) -> None: if global_callback: callbacks = GLOBAL_CALLBACKS.setdefault(_CONTEXT_CALL, []) @@ -313,7 +310,8 @@ def set_waf_callback(value) -> None: def set_waf_info(info: Callable[[], "DDWaf_info"]) -> None: env = _get_asm_context() if env is None: - log.debug("setting waf info with no active asm context") + info_str = add_context_log(log, "appsec.asm_context.warning::set_waf_info::no_active_context") + log.warning(info_str) return env.waf_info = info @@ -325,7 +323,8 @@ def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> if callback: return callback(custom_data, **kwargs) else: - log.warning("WAF callback called but not set") + info = add_context_log(log, "appsec.asm_context.warning::call_waf_callback::not_set") + log.warning(info) return None @@ -378,13 +377,15 @@ def block_request() -> None: if _callable: _callable() else: - log.debug("Block request called but block callable not set by framework") + info = add_context_log(log, "appsec.asm_context.debug::block_request::no_callable") + log.debug(info) def get_data_sent() -> Set[str]: env = _get_asm_context() if env is None: - log.debug("getting addresses sent with no active asm context") + info = add_context_log(log, "appsec.asm_context.debug::get_data_sent::no_asm_context") + log.debug(info) return set() return env.addresses_sent @@ -440,7 +441,8 @@ def store_waf_results_data(data) -> None: return env = _get_asm_context() if env is None: - log.debug("storing waf results data with no active asm context") + info = add_context_log(log, "appsec.asm_context.warning::store_waf_results_data::no_asm_context") + log.warning(info) return for d in data: d["span_id"] = env.span.span_id diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index c20fbb84ee9..9e767d61a53 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -1,3 +1,5 @@ +import logging +import sys from typing import Any import uuid @@ -182,3 +184,10 @@ def get_triggers(span) -> Any: except Exception: log.debug("Failed to parse triggers", exc_info=True) return None + + +def add_context_log(logger: logging.Logger, msg: str, offset: int = 0) -> str: + if sys.version_info < (3, 8): + return msg + filename, line_number, function_name, _stack_info = logger.findCaller(False, 3 + offset) + return f"{msg}[{filename}, line {line_number}, in {function_name}]" diff --git a/tests/appsec/appsec/test_asm_request_context.py b/tests/appsec/appsec/test_asm_request_context.py index ad7fb102cee..f21f8753b3e 100644 --- a/tests/appsec/appsec/test_asm_request_context.py +++ b/tests/appsec/appsec/test_asm_request_context.py @@ -1,8 +1,10 @@ +import mock import pytest from ddtrace.appsec import _asm_request_context from ddtrace.internal._exceptions import BlockingException from tests.appsec.utils import asm_context +from tests.utils import override_global_config _TEST_IP = "1.2.3.4" @@ -120,3 +122,13 @@ def test_blocking_exception_correctly_propagated(): # no more exception there # ensure that the exception was raised and caught at the end of the last context manager assert witness == 1 + + +def test_log_waf_callback(): + with mock.patch("ddtrace.appsec._asm_request_context.log.warning") as mck, override_global_config( + {"_asm_enabled": True} + ): + _asm_request_context.call_waf_callback() + log_message = mck.call_args[0][0] + assert log_message.startswith("appsec.asm_context.warning::call_waf_callback::not_set") + # log message can end with anything here due to tests being instrumented by pytest or other tools From 0d041e5b2793ddfc6a3a7cfeb79fc8f1c601c29c Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:25:47 -0500 Subject: [PATCH 117/372] chore(llmobs): add context & query variable helper fields for helper prompt (#11234) Add the field `rag_context_variables` list and `rag_query_variables` to our helper `Prompt` constructor. This is so users can tell us which prompt template variables to actually use as the ground truth context / question for a faithfulness evaluation. This list will be sent to our backend under the `prompt._dd_context_variables_keys/_dd_query_variable_keys` field. Since this field is only used for internal purposes (our OOTB hallucination eval & potentially RAGAS), there's no reason for it to be a public attribute on the event itself. `_dd_context_variable_keys` will default to `["context"]` if the `Prompt` object exists on a span. `_dd_query_variable_keys` will default to `["question"]` if the `Prompt` object exists on a span. UX: ``` with LLMObs.annotation_context( prompt = Prompt( template="Answer this question: {question} given the following two documents {doc1} {doc2}", variables={"context": rag_context, "question": "When was the first superbowl?"}, rag_context_variables = ["doc1", "doc2"], rag_query_variables = ["question"], ), ): completion = oai_client.chat.completions.create(... ``` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_constants.py | 2 ++ ddtrace/llmobs/_llmobs.py | 14 ++++++++-- ddtrace/llmobs/_trace_processor.py | 1 - ddtrace/llmobs/_utils.py | 25 ++++++++++++++++-- ddtrace/llmobs/utils.py | 15 ++++++++++- tests/llmobs/test_llmobs_service.py | 41 +++++++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index cf915aea35e..5f33349e938 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -54,3 +54,5 @@ RAGAS_ML_APP_PREFIX = "dd-ragas" ANNOTATIONS_CONTEXT_ID = "annotations_context_id" +INTERNAL_CONTEXT_VARIABLE_KEYS = "_dd_context_variable_keys" +INTERNAL_QUERY_VARIABLE_KEYS = "_dd_query_variable_keys" diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 032eafecd4e..07f4d6f93c2 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -281,7 +281,12 @@ def annotation_context( :param prompt: A dictionary that represents the prompt used for an LLM call in the following form: `{"template": "...", "id": "...", "version": "...", "variables": {"variable_1": "...", ...}}`. Can also be set using the `ddtrace.llmobs.utils.Prompt` constructor class. - This argument is only applicable to LLM spans. + - This argument is only applicable to LLM spans. + - The dictionary may contain two optional keys relevant to RAG applications: + `rag_context_variables` - a list of variable key names that contain ground + truth context information + `rag_query_variables` - a list of variable key names that contains query + information for an LLM call :param name: Set to override the span name for any spans annotated within the returned context. """ # id to track an annotation for registering / de-registering @@ -575,7 +580,12 @@ def annotate( :param prompt: A dictionary that represents the prompt used for an LLM call in the following form: `{"template": "...", "id": "...", "version": "...", "variables": {"variable_1": "...", ...}}`. Can also be set using the `ddtrace.llmobs.utils.Prompt` constructor class. - This argument is only applicable to LLM spans. + - This argument is only applicable to LLM spans. + - The dictionary may contain two optional keys relevant to RAG applications: + `rag_context_variables` - a list of variable key names that contain ground + truth context information + `rag_query_variables` - a list of variable key names that contains query + information for an LLM call :param input_data: A single input string, dictionary, or a list of dictionaries based on the span kind: - llm spans: accepts a string, or a dictionary of form {"content": "...", "role": "..."}, or a list of dictionaries with the same signature. diff --git a/ddtrace/llmobs/_trace_processor.py b/ddtrace/llmobs/_trace_processor.py index d6ac9b428ab..b4af0c5ffd1 100644 --- a/ddtrace/llmobs/_trace_processor.py +++ b/ddtrace/llmobs/_trace_processor.py @@ -146,7 +146,6 @@ def _llmobs_span_event(self, span: Span) -> Tuple[Dict[str, Any], bool]: llmobs_span_event["tags"] = self._llmobs_tags( span, ml_app, session_id, is_ragas_integration_span=is_ragas_integration_span ) - return llmobs_span_event, is_ragas_integration_span @staticmethod diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 9c91e6b5744..9228662b7f9 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -1,5 +1,6 @@ import json from typing import Dict +from typing import List from typing import Optional from typing import Union @@ -9,6 +10,8 @@ from ddtrace.ext import SpanTypes from ddtrace.internal.logger import get_logger from ddtrace.llmobs._constants import GEMINI_APM_SPAN_NAME +from ddtrace.llmobs._constants import INTERNAL_CONTEXT_VARIABLE_KEYS +from ddtrace.llmobs._constants import INTERNAL_QUERY_VARIABLE_KEYS from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME from ddtrace.llmobs._constants import ML_APP from ddtrace.llmobs._constants import OPENAI_APM_SPAN_NAME @@ -20,14 +23,16 @@ log = get_logger(__name__) -def validate_prompt(prompt: dict) -> Dict[str, Union[str, dict]]: - validated_prompt = {} # type: Dict[str, Union[str, dict]] +def validate_prompt(prompt: dict) -> Dict[str, Union[str, dict, List[str]]]: + validated_prompt = {} # type: Dict[str, Union[str, dict, List[str]]] if not isinstance(prompt, dict): raise TypeError("Prompt must be a dictionary") variables = prompt.get("variables") template = prompt.get("template") version = prompt.get("version") prompt_id = prompt.get("id") + ctx_variable_keys = prompt.get("rag_context_variables") + rag_query_variable_keys = prompt.get("rag_query_variables") if variables is not None: if not isinstance(variables, dict): raise TypeError("Prompt variables must be a dictionary.") @@ -46,6 +51,22 @@ def validate_prompt(prompt: dict) -> Dict[str, Union[str, dict]]: if not isinstance(prompt_id, str): raise TypeError("Prompt id must be a string.") validated_prompt["id"] = prompt_id + if ctx_variable_keys is not None: + if not isinstance(ctx_variable_keys, list): + raise TypeError("Prompt field `context_variable_keys` must be a list of strings.") + if not all(isinstance(k, str) for k in ctx_variable_keys): + raise TypeError("Prompt field `context_variable_keys` must be a list of strings.") + validated_prompt[INTERNAL_CONTEXT_VARIABLE_KEYS] = ctx_variable_keys + else: + validated_prompt[INTERNAL_CONTEXT_VARIABLE_KEYS] = ["context"] + if rag_query_variable_keys is not None: + if not isinstance(rag_query_variable_keys, list): + raise TypeError("Prompt field `rag_query_variables` must be a list of strings.") + if not all(isinstance(k, str) for k in rag_query_variable_keys): + raise TypeError("Prompt field `rag_query_variables` must be a list of strings.") + validated_prompt[INTERNAL_QUERY_VARIABLE_KEYS] = rag_query_variable_keys + else: + validated_prompt[INTERNAL_QUERY_VARIABLE_KEYS] = ["question"] return validated_prompt diff --git a/ddtrace/llmobs/utils.py b/ddtrace/llmobs/utils.py index 28ff96fdc3d..dac1f3149c9 100644 --- a/ddtrace/llmobs/utils.py +++ b/ddtrace/llmobs/utils.py @@ -19,7 +19,20 @@ ExportedLLMObsSpan = TypedDict("ExportedLLMObsSpan", {"span_id": str, "trace_id": str}) Document = TypedDict("Document", {"name": str, "id": str, "text": str, "score": float}, total=False) Message = TypedDict("Message", {"content": str, "role": str}, total=False) -Prompt = TypedDict("Prompt", {"variables": Dict[str, str], "template": str, "id": str, "version": str}, total=False) +Prompt = TypedDict( + "Prompt", + { + "variables": Dict[str, str], + "template": str, + "id": str, + "version": str, + "rag_context_variables": List[ + str + ], # a list of variable key names that contain ground truth context information + "rag_query_variables": List[str], # a list of variable key names that contains query information + }, + total=False, +) class Messages: diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index ba43ecad56a..160023f5df7 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -777,6 +777,31 @@ def test_annotate_prompt_dict(LLMObs): "variables": {"var1": "var1", "var2": "var3"}, "version": "1.0.0", "id": "test_prompt", + "_dd_context_variable_keys": ["context"], + "_dd_query_variable_keys": ["question"], + } + + +def test_annotate_prompt_dict_with_context_var_keys(LLMObs): + with LLMObs.llm(model_name="test_model") as span: + LLMObs.annotate( + span=span, + prompt={ + "template": "{var1} {var3}", + "variables": {"var1": "var1", "var2": "var3"}, + "version": "1.0.0", + "id": "test_prompt", + "rag_context_variables": ["var1", "var2"], + "rag_query_variables": ["user_input"], + }, + ) + assert json.loads(span.get_tag(INPUT_PROMPT)) == { + "template": "{var1} {var3}", + "variables": {"var1": "var1", "var2": "var3"}, + "version": "1.0.0", + "id": "test_prompt", + "_dd_context_variable_keys": ["var1", "var2"], + "_dd_query_variable_keys": ["user_input"], } @@ -789,6 +814,8 @@ def test_annotate_prompt_typed_dict(LLMObs): variables={"var1": "var1", "var2": "var3"}, version="1.0.0", id="test_prompt", + rag_context_variables=["var1", "var2"], + rag_query_variables=["user_input"], ), ) assert json.loads(span.get_tag(INPUT_PROMPT)) == { @@ -796,6 +823,8 @@ def test_annotate_prompt_typed_dict(LLMObs): "variables": {"var1": "var1", "var2": "var3"}, "version": "1.0.0", "id": "test_prompt", + "_dd_context_variable_keys": ["var1", "var2"], + "_dd_query_variable_keys": ["user_input"], } @@ -1778,7 +1807,11 @@ def test_annotation_context_modifies_span_tags(LLMObs): def test_annotation_context_modifies_prompt(LLMObs): with LLMObs.annotation_context(prompt={"template": "test_template"}): with LLMObs.llm(name="test_agent", model_name="test") as span: - assert json.loads(span.get_tag(INPUT_PROMPT)) == {"template": "test_template"} + assert json.loads(span.get_tag(INPUT_PROMPT)) == { + "template": "test_template", + "_dd_context_variable_keys": ["context"], + "_dd_query_variable_keys": ["question"], + } def test_annotation_context_modifies_name(LLMObs): @@ -1917,7 +1950,11 @@ async def test_annotation_context_async_modifies_span_tags(LLMObs): async def test_annotation_context_async_modifies_prompt(LLMObs): async with LLMObs.annotation_context(prompt={"template": "test_template"}): with LLMObs.llm(name="test_agent", model_name="test") as span: - assert json.loads(span.get_tag(INPUT_PROMPT)) == {"template": "test_template"} + assert json.loads(span.get_tag(INPUT_PROMPT)) == { + "template": "test_template", + "_dd_context_variable_keys": ["context"], + "_dd_query_variable_keys": ["question"], + } async def test_annotation_context_async_modifies_name(LLMObs): From 55d2d75fe3a4a603ab720dc410366617b0251c58 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:07:50 +0100 Subject: [PATCH 118/372] fix(asm): put back email/login/name in traces for django user (#11288) APPSEC-55708 APMS-13572 - Add `DD_DJANGO_INCLUDE_USER_EMAIL` (default to false), `DD_DJANGO_INCLUDE_USER_LOGIN` (default to true) and `DD_DJANGO_INCLUDE_USER_REALNAME` (default to false) to specifically add email/login/real_name to the span tags if user event mode is "identification" - Add `APPSEC_AUTO_EVENTS_EXTENDED` system tests scenario to CI (to test that feature) - Update appropriate tests - Update Django integration documentation ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/system-tests.yml | 4 + ddtrace/appsec/_trace_utils.py | 24 ++++-- ddtrace/appsec/_utils.py | 14 ++-- ddtrace/contrib/django/__init__.py | 27 ++++++- ddtrace/contrib/internal/django/patch.py | 10 ++- ddtrace/contrib/internal/django/utils.py | 1 - ddtrace/settings/asm.py | 10 +++ ...r_optional_user_info-28a1ce33eeb6a275.yaml | 7 ++ tests/contrib/django/test_django_appsec.py | 77 ++++++++++++++++--- tests/telemetry/test_writer.py | 4 + 10 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 releasenotes/notes/ato_fix__feature_flag_for_optional_user_info-28a1ce33eeb6a275.yaml diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 2df85e4d6ea..8d4a6358d75 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -165,6 +165,10 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec' run: ./run.sh APPSEC_MISSING_RULES + - name: Run APPSEC_AUTO_EVENTS_EXTENDED + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec' + run: ./run.sh APPSEC_AUTO_EVENTS_EXTENDED + - name: Run APPSEC_CUSTOM_RULES if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec' run: ./run.sh APPSEC_CUSTOM_RULES diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py index a6839f62ce6..9f7404d0973 100644 --- a/ddtrace/appsec/_trace_utils.py +++ b/ddtrace/appsec/_trace_utils.py @@ -118,6 +118,8 @@ def track_user_login_success_event( real_mode = login_events_mode if login_events_mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode if real_mode == LOGIN_EVENTS_MODE.DISABLED: return + if real_mode == LOGIN_EVENTS_MODE.ANON: + login = name = email = None span = _track_user_login_common(tracer, True, metadata, login_events_mode, login, name, email, span) if not span: return @@ -299,13 +301,17 @@ def _on_django_login( user, mode, info_retriever, + django_config, ): if user: from ddtrace.contrib.django.compat import user_is_authenticated if user_is_authenticated(user): - user_id = info_retriever.get_userid() - + user_id, user_extra = info_retriever.get_user_info( + login=django_config.include_user_login, + email=django_config.include_user_email, + name=django_config.include_user_realname, + ) with pin.tracer.trace("django.contrib.auth.login", span_type=SpanTypes.AUTH): session_key = getattr(request, "session_key", None) track_user_login_success_event( @@ -314,6 +320,7 @@ def _on_django_login( session_id=session_key, propagate=True, login_events_mode=mode, + **user_extra, ) else: # Login failed and the user is unknown (may exist or not) @@ -321,7 +328,7 @@ def _on_django_login( track_user_login_failure_event(pin.tracer, user_id=user_id, login_events_mode=mode) -def _on_django_auth(result_user, mode, kwargs, pin, info_retriever): +def _on_django_auth(result_user, mode, kwargs, pin, info_retriever, django_config): if not asm_config._asm_enabled: return True, result_user @@ -338,12 +345,13 @@ def _on_django_auth(result_user, mode, kwargs, pin, info_retriever): with pin.tracer.trace("django.contrib.auth.login", span_type=SpanTypes.AUTH): exists = info_retriever.user_exists() if exists: - user_id = info_retriever.get_userid() + user_id, user_extra = info_retriever.get_user_info( + login=django_config.include_user_login, + email=django_config.include_user_email, + name=django_config.include_user_realname, + ) track_user_login_failure_event( - pin.tracer, - user_id=user_id, - login_events_mode=mode, - exists=True, + pin.tracer, user_id=user_id, login_events_mode=mode, exists=True, **user_extra ) else: track_user_login_failure_event(pin.tracer, user_id=user_id, login_events_mode=mode, exists=False) diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index 9e767d61a53..e2d46fe098e 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -145,7 +145,7 @@ def get_name(self): return self.find_in_user_model(self.possible_name_fields) - def get_user_info(self): + def get_user_info(self, login=False, email=False, name=False): """ In safe mode, try to get the user id from the user object. In extended mode, try to also get the username (which will be the returned user_id), @@ -157,12 +157,12 @@ def get_user_info(self): if not user_id: return None, {} - user_extra_info = { - "login": self.get_username(), - "email": self.get_user_email(), - "name": self.get_name(), - } - + if login: + user_extra_info["login"] = self.get_username() + if email: + user_extra_info["email"] = self.get_user_email() + if name: + user_extra_info["name"] = self.get_name() return user_id, user_extra_info diff --git a/ddtrace/contrib/django/__init__.py b/ddtrace/contrib/django/__init__.py index 265cb6c33a6..55a867ed560 100644 --- a/ddtrace/contrib/django/__init__.py +++ b/ddtrace/contrib/django/__init__.py @@ -125,12 +125,36 @@ .. py:data:: ddtrace.config.django['include_user_name'] - Whether or not to include the authenticated user's username as a tag on the root request span. + Whether or not to include the authenticated user's name/id as a tag on the root request span. Can also be configured via the ``DD_DJANGO_INCLUDE_USER_NAME`` environment variable. Default: ``True`` +.. py:data:: ddtrace.config.django['include_user_email'] + + (ASM) Whether or not to include the authenticated user's email (if available) as a tag on the root request span on a user event. + + Can also be configured via the ``DD_DJANGO_INCLUDE_USER_EMAIL`` environment variable. + + Default: ``False`` + +.. py:data:: ddtrace.config.django['include_user_login'] + + (ASM) Whether or not to include the authenticated user's login (if available) as a tag on the root request span on a user event. + + Can also be configured via the ``DD_DJANGO_INCLUDE_USER_LOGIN`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['include_user_realname'] + + (ASM) Whether or not to include the authenticated user's real name (if available) as a tag on the root request span on a user event. + + Can also be configured via the ``DD_DJANGO_INCLUDE_USER_REALNAME`` environment variable. + + Default: ``False`` + .. py:data:: ddtrace.config.django['use_handler_resource_format'] Whether or not to use the resource format `"{method} {handler}"`. Can also be @@ -176,6 +200,7 @@ .. __: https://www.djangoproject.com/ """ # noqa: E501 + from ddtrace.internal.utils.importlib import require_modules diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index a22f11e4bda..c40f49b627f 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -70,7 +70,10 @@ analytics_enabled=None, # None allows the value to be overridden by the global config analytics_sample_rate=None, trace_query_string=None, # Default to global config - include_user_name=asbool(os.getenv("DD_DJANGO_INCLUDE_USER_NAME", default=True)), + include_user_name=asm_config._django_include_user_name, + include_user_email=asm_config._django_include_user_email, + include_user_login=asm_config._django_include_user_login, + include_user_realname=asm_config._django_include_user_realname, use_handler_with_url_name_resource_format=asbool( os.getenv("DD_DJANGO_USE_HANDLER_WITH_URL_NAME_RESOURCE_FORMAT", default=False) ), @@ -781,7 +784,7 @@ def traced_login(django, pin, func, instance, args, kwargs): try: request = get_argument_value(args, kwargs, 0, "request") user = get_argument_value(args, kwargs, 1, "user") - core.dispatch("django.login", (pin, request, user, mode, _DjangoUserInfoRetriever(user))) + core.dispatch("django.login", (pin, request, user, mode, _DjangoUserInfoRetriever(user), config.django)) except Exception: log.debug("Error while trying to trace Django login", exc_info=True) @@ -794,7 +797,8 @@ def traced_authenticate(django, pin, func, instance, args, kwargs): return result_user try: result = core.dispatch_with_results( - "django.auth", (result_user, mode, kwargs, pin, _DjangoUserInfoRetriever(result_user, credentials=kwargs)) + "django.auth", + (result_user, mode, kwargs, pin, _DjangoUserInfoRetriever(result_user, credentials=kwargs), config.django), ).user if result and result.value[0]: return result.value[1] diff --git a/ddtrace/contrib/internal/django/utils.py b/ddtrace/contrib/internal/django/utils.py index 489088aa78c..c1ce819bae1 100644 --- a/ddtrace/contrib/internal/django/utils.py +++ b/ddtrace/contrib/internal/django/utils.py @@ -317,7 +317,6 @@ def _after_request_tags(pin, span: Span, request, response): # Response can be None in the event that the request failed # We still want to set additional request tags that are resolved # during the request. - try: user = getattr(request, "user", None) if user is not None: diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 07ee66a5d1b..26289209d35 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -172,6 +172,12 @@ class ASMConfig(Env): int, EXPLOIT_PREVENTION.MAX_STACK_TRACE_DEPTH, default=32, validator=_validate_non_negative_int ) + # Django ATO + _django_include_user_name = Env.var(bool, "DD_DJANGO_INCLUDE_USER_NAME", default=True) + _django_include_user_email = Env.var(bool, "DD_DJANGO_INCLUDE_USER_EMAIL", default=False) + _django_include_user_login = Env.var(bool, "DD_DJANGO_INCLUDE_USER_LOGIN", default=True) + _django_include_user_realname = Env.var(bool, "DD_DJANGO_INCLUDE_USER_REALNAME", default=False) + # for tests purposes _asm_config_keys = [ "_asm_enabled", @@ -208,6 +214,10 @@ class ASMConfig(Env): "_ep_max_stack_trace_depth", "_asm_config_keys", "_deduplication_enabled", + "_django_include_user_name", + "_django_include_user_email", + "_django_include_user_login", + "_django_include_user_realname", ] _iast_redaction_numeral_pattern = Env.var( str, diff --git a/releasenotes/notes/ato_fix__feature_flag_for_optional_user_info-28a1ce33eeb6a275.yaml b/releasenotes/notes/ato_fix__feature_flag_for_optional_user_info-28a1ce33eeb6a275.yaml new file mode 100644 index 00000000000..98175b41c9b --- /dev/null +++ b/releasenotes/notes/ato_fix__feature_flag_for_optional_user_info-28a1ce33eeb6a275.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + ASM: The new user events policy is preventing users PII to be added by default as span tags. To allow customers using + the Django auto instrumentation to still have those information, new environment variables have been added. + In particular DD_DJANGO_INCLUDE_EMAIL (false by default), will tag user events with user email as before. + diff --git a/tests/contrib/django/test_django_appsec.py b/tests/contrib/django/test_django_appsec.py index c3a0933370f..fb262918a56 100644 --- a/tests/contrib/django/test_django_appsec.py +++ b/tests/contrib/django/test_django_appsec.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- +import contextlib + import pytest +from ddtrace import config from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import LOGIN_EVENTS_MODE from ddtrace.appsec._constants import SPAN_DATA_NAMES @@ -14,6 +17,24 @@ from tests.utils import override_global_config +@contextlib.contextmanager +def update_django_config(): + initial_settings = ( + config.django.include_user_email, + config.django.include_user_login, + config.django.include_user_realname, + ) + config.django.include_user_email = asm_config._django_include_user_email + config.django.include_user_login = asm_config._django_include_user_login + config.django.include_user_realname = asm_config._django_include_user_realname + yield + ( + config.django.include_user_email, + config.django.include_user_login, + config.django.include_user_realname, + ) = initial_settings + + def _aux_appsec_get_root_span( client, test_spans, @@ -142,11 +163,23 @@ def test_django_login_events_disabled_noappsec(client, test_spans, tracer): @pytest.mark.django_db -def test_django_login_sucess_identification(client, test_spans, tracer): +@pytest.mark.parametrize("use_login", (False, True)) +@pytest.mark.parametrize("use_email", (False, True)) +@pytest.mark.parametrize("use_realname", (False, True)) +def test_django_login_sucess_identification(client, test_spans, tracer, use_login, use_email, use_realname): from django.contrib.auth import get_user from django.contrib.auth.models import User - with override_global_config(dict(_asm_enabled=True, _auto_user_instrumentation_local_mode=LOGIN_EVENTS_MODE.IDENT)): + with override_global_config( + dict( + _asm_enabled=True, + _auto_user_instrumentation_local_mode=LOGIN_EVENTS_MODE.IDENT, + _django_include_user_email=use_email, + _django_include_user_login=use_login, + _django_include_user_realname=use_realname, + ) + ), update_django_config(): + # update django config for tests test_user = User.objects.create(username="fred", first_name="Fred", email="fred@test.com") test_user.set_password("secret") test_user.save() @@ -158,17 +191,41 @@ def test_django_login_sucess_identification(client, test_spans, tracer): assert login_span.get_tag(user.ID) == "1" assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC + ".success.track") == "true" assert login_span.get_tag(APPSEC.AUTO_LOGIN_EVENTS_SUCCESS_MODE) == LOGIN_EVENTS_MODE.IDENT - assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") is None - assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.email") is None - assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.username") is None + if use_login: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") == "fred" + else: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") is None + if use_email: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.email") == "fred@test.com" + assert login_span.get_tag("usr.email") == "fred@test.com" + else: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.email") is None + assert login_span.get_tag("usr.email") is None + if use_realname: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.username") == "Fred" + assert login_span.get_tag("usr.name") == "Fred" + else: + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.username") is None + assert login_span.get_tag("usr.name") is None @pytest.mark.django_db -def test_django_login_sucess_anonymization(client, test_spans, tracer): +@pytest.mark.parametrize("use_login", (False, True)) +@pytest.mark.parametrize("use_email", (False, True)) +@pytest.mark.parametrize("use_realname", (False, True)) +def test_django_login_sucess_anonymization(client, test_spans, tracer, use_login, use_email, use_realname): from django.contrib.auth import get_user from django.contrib.auth.models import User - with override_global_config(dict(_asm_enabled=True, _auto_user_instrumentation_local_mode=LOGIN_EVENTS_MODE.ANON)): + with override_global_config( + dict( + _asm_enabled=True, + _auto_user_instrumentation_local_mode=LOGIN_EVENTS_MODE.ANON, + _django_include_user_email=use_email, + _django_include_user_login=use_login, + _django_include_user_realname=use_realname, + ) + ), update_django_config(): test_user = User.objects.create(username="fred2") test_user.set_password("secret") test_user.save() @@ -180,9 +237,9 @@ def test_django_login_sucess_anonymization(client, test_spans, tracer): assert login_span.get_tag(user.ID) == "1" assert login_span.get_tag("appsec.events.users.login.success.track") == "true" assert login_span.get_tag(APPSEC.AUTO_LOGIN_EVENTS_SUCCESS_MODE) == LOGIN_EVENTS_MODE.ANON - assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") - assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC + ".success.email") - assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC + ".success.username") + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") is None + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.email") is None + assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.username") is None @pytest.mark.django_db diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 57fbe843c37..4adc232cc6e 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -333,6 +333,10 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_CRASHTRACKING_TAGS", "origin": "default", "value": ""}, {"name": "DD_CRASHTRACKING_WAIT_FOR_RECEIVER", "origin": "default", "value": True}, {"name": "DD_DATA_STREAMS_ENABLED", "origin": "env_var", "value": True}, + {"name": "DD_DJANGO_INCLUDE_USER_EMAIL", "origin": "default", "value": False}, + {"name": "DD_DJANGO_INCLUDE_USER_LOGIN", "origin": "default", "value": True}, + {"name": "DD_DJANGO_INCLUDE_USER_NAME", "origin": "default", "value": True}, + {"name": "DD_DJANGO_INCLUDE_USER_REALNAME", "origin": "default", "value": False}, {"name": "DD_DOGSTATSD_PORT", "origin": "default", "value": None}, {"name": "DD_DOGSTATSD_URL", "origin": "default", "value": None}, {"name": "DD_DYNAMIC_INSTRUMENTATION_DIAGNOSTICS_INTERVAL", "origin": "default", "value": 3600}, From dec7ac1d194e560ebbbee9f0b93f8166af39c1c1 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 7 Nov 2024 18:22:25 +0100 Subject: [PATCH 119/372] chore(asm): endpoint configuration with retries and timeout (#11302) --- ddtrace/constants.py | 2 + ddtrace/settings/endpoint_config.py | 72 ++++++++++++++----- .../integration/http_config_server.py | 12 +++- tests/appsec/iast/test_env_var.py | 49 +++++++++++-- tests/tracer/test_endpoint_config.py | 54 +++++++++++++- 5 files changed, 160 insertions(+), 29 deletions(-) diff --git a/ddtrace/constants.py b/ddtrace/constants.py index 8361dc57e12..1a112f677b8 100644 --- a/ddtrace/constants.py +++ b/ddtrace/constants.py @@ -32,6 +32,8 @@ APPSEC_ENV = "DD_APPSEC_ENABLED" CONFIG_ENDPOINT_ENV = "_DD_CONFIG_ENDPOINT" +CONFIG_ENDPOINT_RETRIES_ENV = "_DD_CONFIG_ENDPOINT_RETRIES" +CONFIG_ENDPOINT_TIMEOUT_ENV = "_DD_CONFIG_ENDPOINT_TIMEOUT" IAST_ENV = "DD_IAST_ENABLED" MANUAL_DROP_KEY = "manual.drop" diff --git a/ddtrace/settings/endpoint_config.py b/ddtrace/settings/endpoint_config.py index 78368078b9a..e721df65a10 100644 --- a/ddtrace/settings/endpoint_config.py +++ b/ddtrace/settings/endpoint_config.py @@ -3,17 +3,57 @@ The configuration endpoint is a URL that returns a JSON object with the configuration for the products. It takes precedence over environment variables and configuration files. """ -import json import os -from urllib import parse from ddtrace.constants import CONFIG_ENDPOINT_ENV +from ddtrace.constants import CONFIG_ENDPOINT_RETRIES_ENV +from ddtrace.constants import CONFIG_ENDPOINT_TIMEOUT_ENV +from ddtrace.internal.constants import DEFAULT_TIMEOUT from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.http import connector +from ddtrace.internal.utils.http import Response +from ddtrace.internal.utils.http import get_connection +from ddtrace.internal.utils.http import verify_url +from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter log = get_logger(__name__) +RETRIES = 1 +try: + if CONFIG_ENDPOINT_RETRIES_ENV in os.environ: + RETRIES = int(os.getenv(CONFIG_ENDPOINT_RETRIES_ENV, str(RETRIES))) +except ValueError: + log.error("Invalid value for %s. Using default value: %s", CONFIG_ENDPOINT_RETRIES_ENV, RETRIES) + + +def _get_retries(): + return RETRIES + + +TIMEOUT = DEFAULT_TIMEOUT +try: + if CONFIG_ENDPOINT_TIMEOUT_ENV in os.environ: + TIMEOUT = int(os.getenv(CONFIG_ENDPOINT_TIMEOUT_ENV, str(TIMEOUT))) +except ValueError: + log.error("Invalid value for %s. Using default value: %s", CONFIG_ENDPOINT_TIMEOUT_ENV, TIMEOUT) + + +def _get_timeout(): + return TIMEOUT + + +def _do_request(url: str) -> Response: + try: + parsed_url = verify_url(url) + url_path = parsed_url.path + conn = get_connection(url, timeout=_get_timeout()) + conn.request("GET", url_path) + response = conn.getresponse() + result = Response.from_http_response(response) + finally: + conn.close() + return result + def fetch_config_from_endpoint() -> dict: """ @@ -26,20 +66,16 @@ def fetch_config_from_endpoint() -> dict: return {} try: - parsed_url = parse.urlparse(config_endpoint) - connect = connector(config_endpoint) - with connect() as conn: - conn.request("GET", parsed_url.path or "/") - response = conn.getresponse() - if not (200 <= response.status < 300): - log.error("Failed to fetch configuration from endpoint, status code: %d", response.status) - return {} - - data = response.read().decode("utf-8") - log.debug("Fetched configuration from endpoint: %s", data) - return json.loads(data) - - except (json.JSONDecodeError, Exception): - log.error("Failed to fetch configuration from endpoint:", exc_info=True) + # DEV: This can also work as a decorator, + # but it's harder to mock the retries in the tests. + res = fibonacci_backoff_with_jitter( + attempts=_get_retries(), + initial_wait=0.1, + until=lambda resp: hasattr(resp, "status") and (200 <= resp.status < 300), + )(_do_request)(config_endpoint) + + return res.get_json() or {} + except Exception: + log.error("Failed to fetch configuration from endpoint", exc_info=True) return {} diff --git a/tests/appsec/iast/fixtures/integration/http_config_server.py b/tests/appsec/iast/fixtures/integration/http_config_server.py index 1dc5793a402..10d6c07304d 100644 --- a/tests/appsec/iast/fixtures/integration/http_config_server.py +++ b/tests/appsec/iast/fixtures/integration/http_config_server.py @@ -1,7 +1,9 @@ from http.server import BaseHTTPRequestHandler from http.server import HTTPServer import json +import random import sys +import time IAST_ENABLED = {"iast_enabled": True} @@ -13,7 +15,15 @@ class SimpleHandler(BaseHTTPRequestHandler): def do_GET(self): # Define the response payload (fixed JSON response) path = self.path[:-1] if self.path.endswith("/") else self.path - if path.endswith("IAST_ENABLED"): + if path.endswith("IAST_ENABLED_TIMEOUT"): + time.sleep(4) + response = IAST_ENABLED + elif random.randint(0, 5) < 3: + self.send_response(500) # Simulate some internal server errors + self.end_headers() + self.wfile.write(b"Internal Server Error") + return + elif path.endswith("IAST_ENABLED"): response = IAST_ENABLED elif path.endswith("IAST_DISABLED"): response = IAST_DISABLED diff --git a/tests/appsec/iast/test_env_var.py b/tests/appsec/iast/test_env_var.py index a1fcac3cc54..57604815aac 100644 --- a/tests/appsec/iast/test_env_var.py +++ b/tests/appsec/iast/test_env_var.py @@ -56,9 +56,25 @@ def test_env_var_iast_unset(monkeypatch, capfd): "env_vars", [ {"DD_IAST_ENABLED": "true"}, - {"DD_IAST_ENABLED": "true", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, - {"DD_IAST_ENABLED": "false", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED"}, - {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED"}, + { + "DD_IAST_ENABLED": "true", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, + { + "DD_IAST_ENABLED": "false", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, + { + "DD_IAST_ENABLED": "false", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED_TIMEOUT", + "_DD_CONFIG_ENDPOINT_TIMEOUT": "5", + }, + { + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, ], ) def test_env_var_iast_enabled_parametrized(capfd, configuration_endpoint, env_vars): @@ -76,10 +92,29 @@ def test_env_var_iast_enabled_parametrized(capfd, configuration_endpoint, env_va [ {}, {"DD_IAST_ENABLED": "false"}, - {"DD_IAST_ENABLED": "true", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED"}, - {"DD_IAST_ENABLED": "false", "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, - {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED"}, - {"_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/"}, + { + "DD_IAST_ENABLED": "true", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, + { + "DD_IAST_ENABLED": "false", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, + { + "DD_IAST_ENABLED": "false", + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_ENABLED_TIMEOUT", + "_DD_CONFIG_ENDPOINT_TIMEOUT": "2", + }, + { + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/IAST_DISABLED", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, + { + "_DD_CONFIG_ENDPOINT": f"http://localhost:{CONFIG_SERVER_PORT}/", + "_DD_CONFIG_ENDPOINT_RETRIES": "10", + }, ], ) def test_env_var_iast_disabled_parametrized(capfd, configuration_endpoint, env_vars): diff --git a/tests/tracer/test_endpoint_config.py b/tests/tracer/test_endpoint_config.py index 7e356827d9a..d1d2e484002 100644 --- a/tests/tracer/test_endpoint_config.py +++ b/tests/tracer/test_endpoint_config.py @@ -9,6 +9,28 @@ from tests.utils import override_env +def mock_getresponse_enabled_after_4_retries(self): + if not hasattr(mock_getresponse_enabled_after_4_retries, "call_count"): + mock_getresponse_enabled_after_4_retries.call_count = 0 + + mock_getresponse_enabled_after_4_retries.call_count += 1 + + response = mock.Mock(spec=HTTPResponse) + response.chunked = False + if mock_getresponse_enabled_after_4_retries.call_count < 4: + response.read.return_value = b"{}" + response.status = 500 + response.reason = "KO" + else: + response.read.return_value = b'{"dd_iast_enabled": true}' + response.status = 200 + response.reason = "OK" + response.fp = BytesIO(response.read.return_value) + response.length = len(response.fp.getvalue()) + response.msg = {"Content-Length": response.length} + return response + + def mock_getresponse_enabled(self): response = mock.Mock(spec=HTTPResponse) response.read.return_value = b'{"dd_iast_enabled": true}' @@ -88,7 +110,8 @@ def test_set_config_endpoint_500(caplog): HTTPConnection, "getresponse", new=mock_getresponse_500 ): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint, status code: 500" in caplog.text + assert "Failed to fetch configuration from endpoint" in caplog.text + assert "RetryError: Response(status=500" in caplog.text def test_set_config_endpoint_403(caplog): @@ -99,7 +122,8 @@ def test_set_config_endpoint_403(caplog): HTTPConnection, "getresponse", new=mock_getresponse_403 ): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint, status code: 403" in caplog.text + assert "Failed to fetch configuration from endpoint" in caplog.text + assert "RetryError: Response(status=403" in caplog.text def test_set_config_endpoint_malformed(caplog): @@ -110,7 +134,6 @@ def test_set_config_endpoint_malformed(caplog): HTTPConnection, "getresponse", new=mock_getresponse_malformed ): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint" in caplog.text assert "Expecting property name enclosed in double quotes" in caplog.text @@ -122,3 +145,28 @@ def test_set_config_endpoint_connection_refused(caplog): assert any( message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") ), "None of the expected connection error log messages were found" + + +def test_set_config_endpoint_timeout_error(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch( + "ddtrace.internal.utils.http.get_connection", side_effect=TimeoutError + ): + assert fetch_config_from_endpoint() == {} + assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text + assert "Failed to fetch configuration from endpoint" in caplog.text + assert any( + message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") + ), "None of the expected connection error log messages were found" + + +def test_set_config_endpoint_retries(caplog): + caplog.set_level(10) + with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object( + HTTPConnection, "connect", new=mock_pass + ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + HTTPConnection, "getresponse", new=mock_getresponse_enabled_after_4_retries + ), mock.patch( + "ddtrace.settings.endpoint_config._get_retries", return_value=5 + ): + assert fetch_config_from_endpoint() == {"dd_iast_enabled": True} From 00c984ca757c2dc92fbfc0fb82254fcb660f838b Mon Sep 17 00:00:00 2001 From: Augusto de Oliveira Date: Thu, 7 Nov 2024 19:03:32 +0100 Subject: [PATCH 120/372] chore(ci): disable macrobenchmark crash tracking to avoid signaling irrelevant crashes (#11321) --- .gitlab/macrobenchmarks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/macrobenchmarks.yml b/.gitlab/macrobenchmarks.yml index fb2f6cc5690..3d901dae66c 100644 --- a/.gitlab/macrobenchmarks.yml +++ b/.gitlab/macrobenchmarks.yml @@ -42,6 +42,7 @@ variables: DD_RUNTIME_METRICS_ENABLED: "true" DD_REMOTE_CONFIGURATION_ENABLED: "false" DD_INSTRUMENTATION_TELEMETRY_ENABLED: "false" + DD_CRASHTRACKING_ENABLED: "false" K6_OPTIONS_WARMUP_RATE: 40 K6_OPTIONS_WARMUP_DURATION: 1m From 273ef620592dde2a58c65216971a3ccbc3f5f283 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:16:20 -0500 Subject: [PATCH 121/372] chore(ci): remove unused type ignore comments (#11327) We started to see typing check failures in the `pre_check` test. This was probably due to side effects that were uncaught from not requiring branches to be updated with main before merging. This PR aims to fix the issue that the typing check is complaining about, but the actual fix to prevent this will be to require main updates on branches before merging (this is already done if using the merge queue, but not manual merges). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_evaluators/ragas/faithfulness.py | 2 +- ddtrace/llmobs/_evaluators/runner.py | 2 +- ddtrace/llmobs/_evaluators/sampler.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py index f108619f30b..9b0abbd8953 100644 --- a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py +++ b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py @@ -165,7 +165,7 @@ def run_and_submit_evaluation(self, span_event: dict): return score_result_or_failure = self.evaluate(span_event) telemetry_writer.add_count_metric( - TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore + TELEMETRY_APM_PRODUCT.LLMOBS, "evaluators.run", 1, tags=( diff --git a/ddtrace/llmobs/_evaluators/runner.py b/ddtrace/llmobs/_evaluators/runner.py index b6a6bc9e844..bf45e618e01 100644 --- a/ddtrace/llmobs/_evaluators/runner.py +++ b/ddtrace/llmobs/_evaluators/runner.py @@ -56,7 +56,7 @@ def __init__(self, interval: float, llmobs_service=None, evaluators=None): raise e finally: telemetry_writer.add_count_metric( - namespace=TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore + namespace=TELEMETRY_APM_PRODUCT.LLMOBS, name="evaluators.init", value=1, tags=( diff --git a/ddtrace/llmobs/_evaluators/sampler.py b/ddtrace/llmobs/_evaluators/sampler.py index 82057c12176..a959e127606 100644 --- a/ddtrace/llmobs/_evaluators/sampler.py +++ b/ddtrace/llmobs/_evaluators/sampler.py @@ -104,7 +104,7 @@ def parsing_failed_because(msg, maybe_throw_this): span_name = rule.get(EvaluatorRunnerSamplingRule.SPAN_NAME_KEY, SamplingRule.NO_RULE) evaluator_label = rule.get(EvaluatorRunnerSamplingRule.EVALUATOR_LABEL_KEY, SamplingRule.NO_RULE) telemetry_writer.add_distribution_metric( - TELEMETRY_APM_PRODUCT.LLMOBS, # type: ignore + TELEMETRY_APM_PRODUCT.LLMOBS, "evaluators.rule_sample_rate", sample_rate, tags=(("evaluator_label", evaluator_label), ("span_name", span_name)), From b3688e2e900dc8d234e8345ddc2a68048b013e5d Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:11:18 -0500 Subject: [PATCH 122/372] chore: update changelog for version 2.16.0, 2.15.2, 2.14.6 (#11293) - [x] update changelog for version 2.16.0, 2.15.2, 2.14.6 --- CHANGELOG.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08895fb1035..17541db9926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,89 @@ Changelogs for versions not listed here can be found at https://github.com/DataD --- -## 2.15.1 +## 2.14.6 + +### Bug Fixes + +- Profiling + - Fixes an issue where enabling native exporter via `DD_PROFILING_EXPORT_LIBDD_ENABLED`, `DD_PROFILING_TIMELINE_ENABLED` or `DD_PROFILING_STACK_V2_ENABLED` turned off live heap profiling. + - Fixes an issue where the profiler was allocating too much memory from `ensure_binary_or_empty()` function, on Python versions before 3.12, with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + - When a Python thread finishes, this change frees memory used for mapping its thread id to `Span`. The mapping is populated and used when `DD_PROFILING_ENDPOINT_COLLECTION_ENABLED` and `DD_PROFILING_STACK_V2_ENABLED` were set to enable grouping of profiles for endpoints. + - Resolves an issue where asyncio task names are not captured by stack v2, when `DD_PROFILING_STACK_V2_ENABLED` is set. + +- Tracing + - pymongo: Adds type checking to solve an issue where `NoneType` instead of expected `Pin` object would throw an error in `TracedTopology` method. + + +--- + +## 2.15.2 + +### Bug Fixes + +- Profiling: + - Fixes an issue where enabling native exporter via `DD_PROFILING_EXPORT_LIBDD_ENABLED`, `DD_PROFILING_TIMELINE_ENABLED` or `DD_PROFILING_STACK_V2_ENABLED` turned off live heap profiling. + - Fixes an issue where the profiler was allocating too much memory from `ensure_binary_or_empty()` function, on Python versions before 3.12, with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + - When a Python thread finishes, this change frees memory used for mapping its thread id to `Span`. The mapping is populated and used when `DD_PROFILING_ENDPOINT_COLLECTION_ENABLED` and `DD_PROFILING_STACK_V2_ENABLED` were set to enable grouping of profiles for endpoints. + - Resolves an issue where asyncio task names are not captured by stack v2, when `DD_PROFILING_STACK_V2_ENABLED` is set. +- Tracing: + - pymongo: Adds type checking to solve an issue where `NoneType` instead of expected `Pin` object would throw an error in `TracedTopology` method. + + +--- + +## 2.16.0 + +### New Features +- LLM Observability + - When starting LLM and embedding spans, the `model_name` argument is now optional and will default to `custom`. This applies to both inline methods (e.g. `LLMObs.llm()`) and function decorators (e.g. `@llm`). + - Introduces the ability to add metadata for evaluation metrics via the `submit_evaluation` method. For more information, see [submitting evaluations with the SDK.](https://docs.datadoghq.com/llm_observability/submit_evaluations/#submitting-evaluations-with-the-sdk) + +- Tracing + - Introduces support for Baggage as defined by the [OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/baggage/api/). + - botocore: Adds span pointers for successful DynamoDB `BatchWriteItem` spans. Table Primary Keys will need to be provided with the `ddtrace.config.botocore.dynamodb_primary_key_names_for_tables` option or the `DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS` environment variable to correctly handle the `PutRequest` items. + - botocore: Adds span pointers for successful DynamoDB `TransactWriteItems` spans. Table Primary Keys will need to be provided with the `ddtrace.config.botocore.dynamodb_primary_key_names_for_tables` option or the `DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS` environment variable to correctly handle the `Put` items. + - botocore: Adds `ddtrace.config.botocore.add_span_pointers` option or the `DD_BOTOCORE_ADD_SPAN_POINTERS` environment variable to control adding span pointers to some successful AWS API requests. This option is enabled by default. + +### Bug Fixes +- CI Visibility + - Fixes a bug where `CODEOWNERS` would incorrectly fail to discard line-level trailing comments (eg: `@code/owner # my comment` would result in codeowners being parsed as `@code/owner`, `#`, `my`, and `comment`) + - Fixes unnecessary logging of an exception that would appear when trying to upload git metadata in an environment without functioning git (eg: missing `git` binary or `.git` directory) + +- Code Security + - Resolves an issue where importing the `google.cloud.storage.batch` module would fail raising an ImportError + +- Dynamic Instrumentation + - Fixes an issue that prevented dynamic span tags probes from adding the requested tags to the requested span. + +- LLM Observability + - Resolves two issues with annotation contexts: + - annotations registered via annotation contexts were being applied globally. Annotations are now only applied to the current trace context and do not pollute to other threads & processes. + - annotations from nested annotation contexts were applied in a non-deterministic order. Annotations are now applied in the order they were registered. + - Resolves an issue where input and output values equal to zero were not being annotated on workflow, task, agent and tool spans when using `LLMObs.annotate`. + - Resolves errors where the disabled setting was being ignored when forking. + +- Profiling + - Fixes a data race where span information associated with a thread was read and updated concurrently, leading to segfaults. + - Fixes an issue where enabling native exporter via `DD_PROFILING_EXPORT_LIBDD_ENABLED`, `DD_PROFILING_TIMELINE_ENABLED` or `DD_PROFILING_STACK_V2_ENABLED` turned off live heap profiling. + - When a Python thread finishes, this change frees memory used for mapping its thread id to `Span`. The mapping is populated and used when `DD_PROFILING_ENDPOINT_COLLECTION_ENABLED` and `DD_PROFILING_STACK_V2_ENABLED` were set to enable grouping of profiles for endpoints. + - Resolves an issue where asyncio task names are not captured by stack v2, when `DD_PROFILING_STACK_V2_ENABLED` is set. + - Resolves an issue where endpoint profiling for stack v2 throws `TypeError` exception when it is given a `Span` with `None` span_type. + +- Tracing + - Resolves the issue where tracer flares would not be generated if unexpected types were received in the `AGENT_CONFIG` remote configuration product. + - elasticsearch: Resolves an issue where span tags were not fully populated on "sampled" spans, causing metric dimensions to be incorrect when spans were prematurely marked as sampled, including resource_name. + + +### Other Changes +- LLM Observability + - Updates the merging behavior for tags when `LLMObs.annotate` is called multiple times on the same span so that the latest value for a tag key overrides the previous value. + + +--- + +## 2.15.1 ### Bug Fixes From 8230f9efe22efb158ee8c7cbe5d51e39bec4cc3c Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:17:12 -0500 Subject: [PATCH 123/372] chore(tracing): update default service naming algorithm to use an inferred base service (#11270) ## Overview Updates the service naming algo to default to an "inferred base service" when `DD_SERVICE` is not set. The PR aims to remove any uses of `'unnamed-python-service'` as well as when service is `''` For schema version `v1`, the tracer `config.service` previously defaulted to `unnamed-python-service`. If an inferred project name is found, the inferred name will be used instead. For schema version `v0`, the tracer `config.service` previously defaulted to `None` if no service was set. This has also been replaced, but with the caveat that we must check if `config.service == config._inferred_service` when setting service on an "Internal Integration" (ie a server internal to the customer application) via `trace_utils.int_service(...)`. If it does equal, we use the default integration service name, to replicate previous behavior where `config.service` would be `None`. We do this for `v0` because many internal integrations have a default service name that is currently used in place, and we don't want to change any of these names (ie: FastAPI as as service name) and break customers. Addresses [INPLAT-127](https://datadoghq.atlassian.net/browse/INPLAT-127) ## Inferred Service Naming Logic (comes from the SSI project) Algorithm: 1. **Detection Logic**: - The main function, `detect_service`, processes the command-line arguments and checks for executables. At the time being these executable checks are limited to `python`. - Adds 1 detector classes, `PythonDetector`. We can add more in the future for Flask and other popular executables if desired. 2. **PythonDetector**: - Searches for the nearest top-level directory containing a `__init__.py` file or a regular `.py` file. - If the `-m` flag is encountered, it captures the following argument as a module name. - Iterates up the directory tree until it identifies a corresponding package structure, returning names in dot notation. ## Testing The PR required consistent naming for the service naming algorithm. All pytest subprocess fixtures used to run tests were modified to add a `ddtrace_subprocess_dir` within the temp file name. This name will be used now as the fallback if `DD_SERVICE` is unset during subprocess tests. Makes snapshot updates much easier. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) [INPLAT-127]: https://datadoghq.atlassian.net/browse/INPLAT-127?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Brett Langdon --- ddtrace/contrib/internal/aiobotocore/patch.py | 7 +- ddtrace/contrib/trace_utils.py | 9 +- .../internal/schema/span_attribute_schema.py | 10 +- ddtrace/internal/telemetry/writer.py | 6 +- ddtrace/settings/_inferred_base_service.py | 195 ++++++++++++++++++ ddtrace/settings/config.py | 13 ++ ...efault-service-names-56395f9bcb4458de.yaml | 6 + .../api/test_api_fake_runners.py | 1 + tests/conftest.py | 110 ++++++---- tests/contrib/aiobotocore/test.py | 4 +- tests/contrib/aredis/test_aredis.py | 4 +- tests/contrib/asgi/test_asgi.py | 15 +- tests/contrib/django/test_django.py | 18 +- .../test_djangorestframework.py | 6 +- tests/contrib/dogpile_cache/test_tracing.py | 8 +- tests/contrib/elasticsearch/test_async.py | 5 +- .../elasticsearch/test_elasticsearch_multi.py | 5 +- tests/contrib/falcon/test_schematization.py | 6 +- tests/contrib/fastapi/test_fastapi.py | 4 +- tests/contrib/flask/test_request.py | 10 +- tests/contrib/graphql/test_graphql.py | 6 +- tests/contrib/openai/test_openai_v0.py | 2 +- tests/contrib/openai/test_openai_v1.py | 2 +- .../contrib/psycopg/test_psycopg_snapshot.py | 19 +- .../contrib/psycopg2/test_psycopg_snapshot.py | 19 +- tests/contrib/sanic/test_sanic.py | 4 +- tests/contrib/sanic/test_sanic_server.py | 4 +- tests/contrib/wsgi/test_wsgi.py | 8 +- tests/integration/test_integration.py | 2 +- tests/internal/service_name/test_imports.py | 14 +- .../test_inferred_base_service.py | 80 +++++++ tests/internal/service_name/test_processor.py | 8 +- ...ers.test_manual_api_fake_atr_mix_fail.json | 176 ++++++++-------- ...ers.test_manual_api_fake_atr_mix_pass.json | 96 ++++----- ...ers.test_manual_api_fake_efd_all_pass.json | 160 +++++++------- ...st_manual_api_fake_efd_faulty_session.json | 100 ++++----- ...ers.test_manual_api_fake_efd_mix_fail.json | 160 +++++++------- ...ers.test_manual_api_fake_efd_mix_pass.json | 160 +++++++------- ....test_manual_api_fake_runner_all_fail.json | 56 ++--- ..._fake_runner_all_itr_skip_suite_level.json | 44 ++-- ...i_fake_runner_all_itr_skip_test_level.json | 44 ++-- ....test_manual_api_fake_runner_all_pass.json | 44 ++-- ....test_manual_api_fake_runner_all_skip.json | 44 ++-- ....test_manual_api_fake_runner_mix_fail.json | 110 +++++----- ..._fake_runner_mix_fail_itr_suite_level.json | 146 ++++++------- ...i_fake_runner_mix_fail_itr_test_level.json | 146 ++++++------- ....test_manual_api_fake_runner_mix_pass.json | 72 +++---- ...lient.test_configure_service_name_pin.json | 4 +- ...st_unspecified_service_name_env[None].json | 4 +- ...test_unspecified_service_name_env[v0].json | 4 +- ...test_unspecified_service_name_env[v1].json | 4 +- ...omysql.test_schematized_span_name[v0].json | 2 +- ...omysql.test_schematized_span_name[v1].json | 2 +- ..._aiomysql.test_unspecified_service_v1.json | 2 +- ...dis.test_full_command_in_resource_env.json | 6 +- ...pg.test_configure_service_name_env_v0.json | 4 +- ...pg.test_configure_service_name_env_v1.json | 4 +- ..._asyncpg.test_span_name_by_schema[v0].json | 4 +- ..._asyncpg.test_span_name_by_schema[v1].json | 4 +- ....test_unspecified_service_name_env_v0.json | 4 +- ....test_unspecified_service_name_env_v1.json | 4 +- ...b.cherrypy.test_middleware.test_child.json | 4 +- ...b.cherrypy.test_middleware.test_error.json | 2 +- ...b.cherrypy.test_middleware.test_fatal.json | 2 +- ...cherrypy.test_middleware.test_success.json | 2 +- ...csearch.test_async.test_elasticsearch.json | 4 +- ...search.test_async.test_elasticsearch7.json | 4 +- ...sticsearch.test_async.test_opensearch.json | 4 +- ...lasticsearch_multi.test_elasticsearch.json | 4 +- ...asticsearch_multi.test_elasticsearch7.json | 4 +- ...st_schematization[schema_tuples0]_6_9.json | 6 +- ...test_schematization[schema_tuples0]_9.json | 8 +- ...st_schematization[schema_tuples1]_6_9.json | 6 +- ...test_schematization[schema_tuples1]_9.json | 8 +- ...st_schematization[schema_tuples2]_6_9.json | 6 +- ...test_schematization[schema_tuples2]_9.json | 8 +- ...l.test_span_schematization[None-None].json | 8 +- ...hql.test_span_schematization[None-v0].json | 8 +- ...hql.test_span_schematization[None-v1].json | 8 +- ...est_schematized_operation_name_env_v0.json | 2 +- ...est_schematized_operation_name_env_v1.json | 2 +- ...tized_unspecified_service_name_env_v1.json | 2 +- ...span_service_and_operation[None-None].json | 4 +- ...d_span_service_and_operation[v0-None].json | 4 +- ...d_span_service_and_operation[v1-None].json | 4 +- ...t_kafka.test_service_override_env_var.json | 4 +- ...est_langchain.test_openai_integration.json | 6 +- ...n.test_openai_service_name[None-None].json | 6 +- ...ain.test_openai_service_name[None-v0].json | 6 +- ...ain.test_openai_service_name[None-v1].json | 6 +- ...ain_community.test_openai_integration.json | 4 +- ...y.test_openai_service_name[None-None].json | 4 +- ...ity.test_openai_service_name[None-v0].json | 4 +- ...ity.test_openai_service_name[None-v1].json | 4 +- ...d_operation[service_schema0]_post_1_1.json | 2 +- ...nd_operation[service_schema0]_pre_1_1.json | 2 +- ...d_operation[service_schema1]_post_1_1.json | 2 +- ...nd_operation[service_schema1]_pre_1_1.json | 2 +- ...d_operation[service_schema2]_post_1_1.json | 2 +- ...nd_operation[service_schema2]_pre_1_1.json | 2 +- ..._dd_mariadb_service_snapshot_post_1_1.json | 2 +- ...d_dd_mariadb_service_snapshot_pre_1_1.json | 2 +- ...ai.test_openai.test_integration_async.json | 20 +- ...nai.test_openai.test_integration_sync.json | 4 +- ...t_integration_service_name[None-None].json | 4 +- ...est_integration_service_name[None-v0].json | 4 +- ...est_integration_service_name[None-v1].json | 4 +- ...test_openai_v1.test_integration_async.json | 51 +++++ ...t_integration_service_name[None-None].json | 2 +- ...est_integration_service_name[None-v0].json | 2 +- ...est_integration_service_name[None-v1].json | 2 +- ....test_openai_v1.test_integration_sync.json | 2 +- ..._psycopg_snapshot.test_connect_traced.json | 2 +- ..._snapshot.test_connect_traced_via_env.json | 30 +++ ..._psycopg_snapshot.test_connect_traced.json | 2 +- ..._snapshot.test_connect_traced_via_env.json | 30 +++ ...ot.test_pytest_will_include_lines_pct.json | 4 - ...ont_include_lines_pct_if_report_empty.json | 4 - ...v2.test_pytest_will_include_lines_pct.json | 4 - ...ont_include_lines_pct_if_report_empty.json | 4 - ...est.test_full_command_in_resource_env.json | 6 +- ....test_schematization[service_schema0].json | 8 +- ...atization[service_schema0]_pre_1_10_1.json | 8 +- ....test_schematization[service_schema1].json | 8 +- ...atization[service_schema1]_pre_1_10_1.json | 8 +- ....test_schematization[service_schema2].json | 8 +- ...atization[service_schema2]_pre_1_10_1.json | 8 +- ...multiple_requests_sanic_http_pre_21.9.json | 8 +- ...c.test_sanic_server.test_sanic_errors.json | 4 +- ...nic_server.test_sanic_errors_pre_21.9.json | 4 +- ....test_schematization[service_schema0].json | 2 +- ....test_schematization[service_schema1].json | 2 +- ....test_schematization[service_schema2].json | 2 +- ...t_unittest_generates_source_file_data.json | 2 +- ....test_unittest_will_include_lines_pct.json | 4 - ...t_wsgi.test_schematization[None-None].json | 8 +- ...est_wsgi.test_schematization[None-v0].json | 8 +- ...est_wsgi.test_schematization[None-v1].json | 12 +- ...dis.test_full_command_in_resource_env.json | 6 +- ....test_schematization[service_schema0].json | 4 +- ....test_schematization[service_schema1].json | 4 +- ....test_schematization[service_schema2].json | 4 +- ...t_snapshots.test_context_multiprocess.json | 4 +- ....test_ddtrace_run_trace_methods_async.json | 8 +- ...s.test_ddtrace_run_trace_methods_sync.json | 14 +- tests/telemetry/test_writer.py | 3 +- tests/tracer/runtime/test_runtime_metrics.py | 12 +- tests/tracer/runtime/test_tag_collectors.py | 13 +- 148 files changed, 1626 insertions(+), 1125 deletions(-) create mode 100644 ddtrace/settings/_inferred_base_service.py create mode 100644 releasenotes/notes/implement-inferred-service-naming-for-default-service-names-56395f9bcb4458de.yaml create mode 100644 tests/internal/service_name/test_inferred_base_service.py create mode 100644 tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_async.json create mode 100644 tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced_via_env.json create mode 100644 tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced_via_env.json diff --git a/ddtrace/contrib/internal/aiobotocore/patch.py b/ddtrace/contrib/internal/aiobotocore/patch.py index e6ba37161ad..c5dafcaaa41 100644 --- a/ddtrace/contrib/internal/aiobotocore/patch.py +++ b/ddtrace/contrib/internal/aiobotocore/patch.py @@ -7,6 +7,7 @@ from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY from ddtrace.constants import SPAN_KIND from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.trace_utils import ext_service from ddtrace.contrib.trace_utils import unwrap from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -56,7 +57,7 @@ def patch(): aiobotocore.client._datadog_patch = True wrapt.wrap_function_wrapper("aiobotocore.client", "AioBaseClient._make_api_call", _wrapped_api_call) - Pin(service=config.service or "aws").onto(aiobotocore.client.AioBaseClient) + Pin().onto(aiobotocore.client.AioBaseClient) def unpatch(): @@ -111,12 +112,12 @@ async def _wrapped_api_call(original_func, instance, args, kwargs): endpoint_name = deep_getattr(instance, "_endpoint._endpoint_prefix") - service = pin.service if pin.service != "aws" else "{}.{}".format(pin.service, endpoint_name) + fallback_service = config._get_service(default="aws.{}".format(endpoint_name)) with pin.tracer.trace( schematize_cloud_api_operation( "{}.command".format(endpoint_name), cloud_provider="aws", cloud_service=endpoint_name ), - service=schematize_service_name(service), + service=ext_service(pin, config.aiobotocore, default=schematize_service_name(fallback_service)), span_type=SpanTypes.HTTP, ) as span: span.set_tag_str(COMPONENT, config.aiobotocore.integration_name) diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py index f0ff816ed43..00ea2a4382d 100644 --- a/ddtrace/contrib/trace_utils.py +++ b/ddtrace/contrib/trace_utils.py @@ -372,12 +372,19 @@ def int_service(pin, int_config, default=None): return cast(str, int_config.service_name) global_service = int_config.global_config._get_service() - if global_service: + # We check if global_service != _inferred_base_service since global service (config.service) + # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not + # use the inferred base service value, and instead use the integration default service. If we + # didn't do this, we would have a massive breaking change from adding inferred_base_service. + if global_service and global_service != int_config.global_config._inferred_base_service: return cast(str, global_service) if "_default_service" in int_config and int_config._default_service is not None: return cast(str, int_config._default_service) + if default is None and global_service: + return cast(str, global_service) + return default diff --git a/ddtrace/internal/schema/span_attribute_schema.py b/ddtrace/internal/schema/span_attribute_schema.py index b0cfbb85689..1ebd95c0527 100644 --- a/ddtrace/internal/schema/span_attribute_schema.py +++ b/ddtrace/internal/schema/span_attribute_schema.py @@ -1,6 +1,10 @@ from enum import Enum +import sys +from typing import Dict +from typing import Optional from ddtrace.internal.constants import DEFAULT_SERVICE_NAME +from ddtrace.settings._inferred_base_service import detect_service class SpanDirection(Enum): @@ -111,5 +115,9 @@ def url_operation_v1(v0_operation, protocol=None, direction=None): }, } +_inferred_base_service: Optional[str] = detect_service(sys.argv) -_DEFAULT_SPAN_SERVICE_NAMES = {"v0": None, "v1": DEFAULT_SERVICE_NAME} +_DEFAULT_SPAN_SERVICE_NAMES: Dict[str, Optional[str]] = { + "v0": _inferred_base_service or None, + "v1": _inferred_base_service or DEFAULT_SERVICE_NAME, +} diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index c6232d0064c..7fbbecf56ae 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -17,6 +17,7 @@ from ...internal import atexit from ...internal import forksafe +from ...settings._inferred_base_service import detect_service from ..agent import get_connection from ..agent import get_trace_url from ..compat import get_connection_response @@ -47,6 +48,9 @@ from .metrics_namespaces import NamespaceMetricType # noqa:F401 +_inferred_service = detect_service(sys.argv) + + log = getLogger(__name__) @@ -54,7 +58,7 @@ class _TelemetryConfig: API_KEY = os.environ.get("DD_API_KEY", None) SITE = os.environ.get("DD_SITE", "datadoghq.com") ENV = os.environ.get("DD_ENV", "") - SERVICE = os.environ.get("DD_SERVICE", "unnamed-python-service") + SERVICE = os.environ.get("DD_SERVICE", _inferred_service or "unnamed-python-service") VERSION = os.environ.get("DD_VERSION", "") AGENTLESS_MODE = asbool(os.environ.get("DD_CIVISIBILITY_AGENTLESS_ENABLED", False)) HEARTBEAT_INTERVAL = float(os.environ.get("DD_TELEMETRY_HEARTBEAT_INTERVAL", "60")) diff --git a/ddtrace/settings/_inferred_base_service.py b/ddtrace/settings/_inferred_base_service.py new file mode 100644 index 00000000000..091c5b5d2e9 --- /dev/null +++ b/ddtrace/settings/_inferred_base_service.py @@ -0,0 +1,195 @@ +import fnmatch +import os +import pathlib +import re +import sys +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple + + +INIT_PY = "__init__.py" +ALL_PY_FILES = "*.py" + +CACHE: Dict[Tuple[str, ...], Optional[str]] = {} + + +class ServiceMetadata: + def __init__(self, name: str): + # prevent service name from being an empty string + self.name = name if name != "" else None + + +class PythonDetector: + def __init__(self, environ: Dict[str, str]): + self.environ = environ + self.name = "python" + + # This pattern matches: + # - Starts with an optional directory (anything before the last '/' or '') + # - Ends with the expected command name, possibly followed by a version + # - Ensures that it does not end with .py + # - Match /python, /python3.7, etc. + self.pattern = r"(^|/)(?!.*\.py$)(" + re.escape("python") + r"(\d+\.\d+)?$)" + + def detect(self, args: List[str]) -> Optional[ServiceMetadata]: + """ + Detects and returns service metadata based on the provided list of arguments. + + This function iterates through the provided arguments, skipping any that are + flags (starting with '-') or environment variable assignments (containing '='). + If a valid module flag ('-m') is encountered, it will switch to module detection mode. + It checks for existing directories and deduces package names from provided paths + to generate service metadata. + + Args: + args (List[str]): A list of command-line arguments. + + Returns: + Optional[ServiceMetadata]: + - ServiceMetadata: The detected service metadata if found. + """ + prev_arg_is_flag = False + module_flag = False + + for arg in args: + has_flag_prefix = arg.startswith("-") + is_env_variable = "=" in arg + + should_skip_arg = prev_arg_is_flag or has_flag_prefix or is_env_variable + + if module_flag: + return ServiceMetadata(arg) + + if not should_skip_arg: + abs_path = pathlib.Path(arg).resolve() + if not abs_path.exists(): + continue + stripped = abs_path + if not stripped.is_dir(): + stripped = stripped.parent + value, ok = self.deduce_package_name(stripped) + if ok: + return ServiceMetadata(value) + return ServiceMetadata(self.find_nearest_top_level(stripped)) + + if has_flag_prefix and arg == "-m": + module_flag = True + + prev_arg_is_flag = has_flag_prefix + + return None + + def deduce_package_name(self, fp: pathlib.Path) -> Tuple[str, bool]: + # Walks the file path until a `__init__.py` is not found. + # All the dir traversed are joined then with `.` + up = pathlib.Path(fp).parent + current = fp + traversed: List[str] = [] + + while current != up: + if not (current / INIT_PY).exists(): + break + traversed.insert(0, current.name) + current = up + up = current.parent + + return ".".join(traversed), len(traversed) > 0 + + def find_nearest_top_level(self, fp: pathlib.Path) -> str: + # returns the top level dir containing a .py file starting walking up from fp + up = fp.parent + current = fp + last = current + + while current != up: + if not fnmatch.filter(os.listdir(current), ALL_PY_FILES): + break + last = current + current = up + up = current.parent + + return last.name + + def matches(self, command: str) -> bool: + # Returns if the command matches the regex pattern for finding python executables / commands. + return bool(re.search(self.pattern, command)) + + +def detect_service(args: List[str]) -> Optional[str]: + """ + Detects and returns the name of a service based on the provided list of command-line arguments. + + This function checks the provided arguments against a list of detector classes to identify + the service type. If any of the arguments represent executables, they are ignored. The + function iterates through the detector instances, applying their detection logic to the qualifying + arguments in order to determine a service name. + + Args: + args (List[str]): A list of command-line arguments. + detector_classes (List[Type[Detector]]): A list of detector classes to use for service detection. + Defaults to [PythonDetector]. + + Returns: + Optional[str]: The name of the detected service, or None if no service was detected. + """ + detector_classes = [PythonDetector] + + if not args: + return None + + cache_key = tuple(sorted(args)) + if cache_key in CACHE: + return CACHE.get(cache_key) + + # Check both the included command args as well as the executable being run + possible_commands = [*args, sys.executable] + executable_args = set() + + # List of detectors to try in order + detectors = {} + for detector_class in detector_classes: + detector_instance = detector_class(dict(os.environ)) + + for i, command in enumerate(possible_commands): + detector_name = detector_instance.name + + if detector_instance.matches(command): + detectors.update({detector_name: detector_instance}) + # append to a list of arg indexes to ignore since they are executables + executable_args.add(i) + continue + elif _is_executable(command): + # append to a list of arg indexes to ignore since they are executables + executable_args.add(i) + + args_to_search = [] + for i, arg in enumerate(args): + # skip any executable args + if i not in executable_args: + args_to_search.append(arg) + + # Iterate through the matched detectors + for detector in detectors.values(): + metadata = detector.detect(args_to_search) + if metadata and metadata.name: + CACHE[cache_key] = metadata.name + return metadata.name + CACHE[cache_key] = None + return None + + +def _is_executable(file_path: str) -> bool: + normalized_path = os.path.normpath(file_path) + if not os.path.isfile(normalized_path): + return False + + # Split the path into directories and check for 'bin' + directory = os.path.dirname(normalized_path) + while directory and os.path.dirname(directory) != directory: # Check to prevent infinite loops + if os.path.basename(directory).endswith("bin"): + return True + directory = os.path.dirname(directory) + + return False diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index aa442574562..1c613f6a1f5 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -36,6 +36,7 @@ from ..internal.utils.formats import parse_tags_str from ..pin import Pin from ._core import get_config as _get_config +from ._inferred_base_service import detect_service from ._otel_remapper import otel_remapping as _otel_remapping from .endpoint_config import fetch_config_from_endpoint from .http import HttpConfig @@ -474,11 +475,14 @@ def __init__(self): self.env = _get_config("DD_ENV", self.tags.get("env")) self.service = _get_config("DD_SERVICE", self.tags.get("service", DEFAULT_SPAN_SERVICE_NAME)) + self._inferred_base_service = detect_service(sys.argv) if self.service is None and in_gcp_function(): self.service = _get_config(["K_SERVICE", "FUNCTION_NAME"], DEFAULT_SPAN_SERVICE_NAME) if self.service is None and in_azure_function(): self.service = _get_config("WEBSITE_SITE_NAME", DEFAULT_SPAN_SERVICE_NAME) + if self.service is None and self._inferred_base_service: + self.service = self._inferred_base_service self._extra_services = set() self._extra_services_queue = None if in_aws_lambda() or not self._remote_config_enabled else File_Queue() @@ -745,6 +749,15 @@ def _get_service(self, default=None): :type default: str :rtype: str|None """ + + # We check if self.service != _inferred_base_service since config.service + # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not + # use the inferred base service value, and instead use the default if included. If we + # didn't do this, we would have a massive breaking change from adding inferred_base_service, + # which would be replacing any integration defaults since service is no longer None. + if self.service and self.service == self._inferred_base_service: + return default if default is not None else self.service + # TODO: This method can be replaced with `config.service`. return self.service if self.service is not None else default diff --git a/releasenotes/notes/implement-inferred-service-naming-for-default-service-names-56395f9bcb4458de.yaml b/releasenotes/notes/implement-inferred-service-naming-for-default-service-names-56395f9bcb4458de.yaml new file mode 100644 index 00000000000..724a12e6803 --- /dev/null +++ b/releasenotes/notes/implement-inferred-service-naming-for-default-service-names-56395f9bcb4458de.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Updates the service naming algorithm to infer the base service name when ``DD_SERVICE`` is not set, replacing + instances of ``'unnamed-python-service'``. Ensures that a more meaningful service name is used whenever + possible, enhancing clarity in service identification. diff --git a/tests/ci_visibility/api/test_api_fake_runners.py b/tests/ci_visibility/api/test_api_fake_runners.py index a2ac51472f9..58f9bb5c5f2 100644 --- a/tests/ci_visibility/api/test_api_fake_runners.py +++ b/tests/ci_visibility/api/test_api_fake_runners.py @@ -27,6 +27,7 @@ "meta.test_module_id", "meta.test_session_id", "meta.test_suite_id", + "meta._dd.base_service", "metrics._dd.top_level", "metrics._dd.tracer_kr", "metrics._sampling_priority_v1", diff --git a/tests/conftest.py b/tests/conftest.py index 0533174236e..be1790e432f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,9 +10,11 @@ from os.path import splitext import platform import random +import shutil import subprocess import sys from tempfile import NamedTemporaryFile +from tempfile import gettempdir import time from typing import Any # noqa:F401 from typing import Generator # noqa:F401 @@ -47,6 +49,9 @@ code_to_pyc = getattr(importlib._bootstrap_external, "_code_to_timestamp_pyc") +DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME = "ddtrace_subprocess_dir" + + # Hack to try and capture more logging data from pytest failing on `internal` jobs on # pytest shutdown. This is a temporary workaround until we can figure out... why.... # https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/68751/workflows/8939123d-e0bf-4fd5-a4f2-2368eb9fc141/jobs/4201092 @@ -156,10 +161,27 @@ def clear_context_after_every_test(): _DD_CONTEXTVAR.set(None) +def create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir): + # Create a test dir named `ddtrace_subprocess_dir` that will be used by the tracers + # inferred path service name as a fallback to DD_SERVICE + ddtrace_dir = tmpdir.join(DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME) + if not ddtrace_dir.exists(): + ddtrace_dir.mkdir() + + # Check for __init__.py and create it if it doesn't exist + # The first dir without an init file aka 'ddtrace_subprocess_dir' will be our service name + init_file = ddtrace_dir.join("__init__.py") + if not init_file.exists(): + init_file.write("") # Create an empty __init__.py file + + pyfile = ddtrace_dir.join("test.py") + return pyfile + + @pytest.fixture def run_python_code_in_subprocess(tmpdir): def _run(code, **kwargs): - pyfile = tmpdir.join("test.py") + pyfile = create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir) pyfile.write(code) return call_program(sys.executable, str(pyfile), **kwargs) @@ -169,7 +191,7 @@ def _run(code, **kwargs): @pytest.fixture def ddtrace_run_python_code_in_subprocess(tmpdir): def _run(code, **kwargs): - pyfile = tmpdir.join("test.py") + pyfile = create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir) pyfile.write(code) return call_program("ddtrace-run", sys.executable, str(pyfile), **kwargs) @@ -305,44 +327,56 @@ def run_function_from_file(item, params=None): expected_out = marker.kwargs.get("out", "") expected_err = marker.kwargs.get("err", "") - with NamedTemporaryFile(mode="wb", suffix=".pyc") as fp: - dump_code_to_file(compile(FunctionDefFinder(func).find(file), file, "exec"), fp.file) - - # If running a module with -m, we change directory to the module's - # folder and run the module directly. - if run_module: - cwd, module = split(splitext(fp.name)[0]) - args.append(module) - else: - cwd = None - args.append(fp.name) + # Create a temporary dir named `ddtrace_subprocess_dir` that will be used for service naming + # consistency + temp_dir = gettempdir() + custom_temp_dir = os.path.join(temp_dir, DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME) - # Add any extra requested args - args.extend(marker.kwargs.get("args", [])) + os.makedirs(custom_temp_dir, exist_ok=True) - def _subprocess_wrapper(): - out, err, status, _ = call_program(*args, env=env, cwd=cwd, timeout=timeout) - - xfailed = b"_pytest.outcomes.XFailed" in err and status == 1 - if xfailed: - pytest.xfail("subprocess test resulted in XFail") - return - - if status != expected_status: - raise AssertionError( - "Expected status %s, got %s." - "\n=== Captured STDOUT ===\n%s=== End of captured STDOUT ===" - "\n=== Captured STDERR ===\n%s=== End of captured STDERR ===" - % (expected_status, status, out.decode("utf-8"), err.decode("utf-8")) - ) - - if not is_stream_ok(out, expected_out): - raise AssertionError("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) - - if not is_stream_ok(err, expected_err): - raise AssertionError("STDERR: Expected [%s] got [%s]" % (expected_err, err)) - - return _subprocess_wrapper() + try: + with NamedTemporaryFile(mode="wb", suffix=".pyc", dir=custom_temp_dir, delete=False) as fp: + dump_code_to_file(compile(FunctionDefFinder(func).find(file), file, "exec"), fp.file) + + # If running a module with -m, we change directory to the module's + # folder and run the module directly. + if run_module: + cwd, module = split(splitext(fp.name)[0]) + args.append(module) + else: + cwd = None + args.append(fp.name) + + # Add any extra requested args + args.extend(marker.kwargs.get("args", [])) + + def _subprocess_wrapper(): + out, err, status, _ = call_program(*args, env=env, cwd=cwd, timeout=timeout) + + xfailed = b"_pytest.outcomes.XFailed" in err and status == 1 + if xfailed: + pytest.xfail("subprocess test resulted in XFail") + return + + if status != expected_status: + raise AssertionError( + "Expected status %s, got %s." + "\n=== Captured STDOUT ===\n%s=== End of captured STDOUT ===" + "\n=== Captured STDERR ===\n%s=== End of captured STDERR ===" + % (expected_status, status, out.decode("utf-8"), err.decode("utf-8")) + ) + + if not is_stream_ok(out, expected_out): + raise AssertionError("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) + + if not is_stream_ok(err, expected_err): + raise AssertionError("STDERR: Expected [%s] got [%s]" % (expected_err, err)) + + return _subprocess_wrapper() + finally: + # Clean up the temporary directory + if os.path.exists(custom_temp_dir): + shutil.rmtree(custom_temp_dir) @pytest.hookimpl(tryfirst=True) diff --git a/tests/contrib/aiobotocore/test.py b/tests/contrib/aiobotocore/test.py index 5c99a1f9f6c..9b31b61abb4 100644 --- a/tests/contrib/aiobotocore/test.py +++ b/tests/contrib/aiobotocore/test.py @@ -7,7 +7,7 @@ from ddtrace.constants import ERROR_MSG from ddtrace.contrib.aiobotocore.patch import patch from ddtrace.contrib.aiobotocore.patch import unpatch -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code from tests.utils import override_config @@ -405,7 +405,7 @@ async def test_user_specified_service(tracer): [ (None, None, "aws.{}", "{}.command"), (None, "v0", "aws.{}", "{}.command"), - (None, "v1", _DEFAULT_SPAN_SERVICE_NAMES["v1"], "aws.{}.request"), + (None, "v1", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "aws.{}.request"), ("mysvc", None, "mysvc", "{}.command"), ("mysvc", "v0", "mysvc", "{}.command"), ("mysvc", "v1", "mysvc", "aws.{}.request"), diff --git a/tests/contrib/aredis/test_aredis.py b/tests/contrib/aredis/test_aredis.py index 9d9ff28dee0..4949a3b19d0 100644 --- a/tests/contrib/aredis/test_aredis.py +++ b/tests/contrib/aredis/test_aredis.py @@ -8,7 +8,7 @@ from ddtrace import Pin from ddtrace.contrib.aredis.patch import patch from ddtrace.contrib.aredis.patch import unpatch -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.opentracer.utils import init_tracer from tests.utils import override_config @@ -139,7 +139,7 @@ async def test_meta_override(tracer, test_spans): [ (None, None, "redis", "redis.command"), (None, "v0", "redis", "redis.command"), - (None, "v1", _DEFAULT_SPAN_SERVICE_NAMES["v1"], "redis.command"), + (None, "v1", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "redis.command"), ("mysvc", None, "redis", "redis.command"), ("mysvc", "v0", "redis", "redis.command"), ("mysvc", "v1", "mysvc", "redis.command"), diff --git a/tests/contrib/asgi/test_asgi.py b/tests/contrib/asgi/test_asgi.py index 16d0b1fdcea..40935990fc2 100644 --- a/tests/contrib/asgi/test_asgi.py +++ b/tests/contrib/asgi/test_asgi.py @@ -10,8 +10,8 @@ from ddtrace.constants import ERROR_MSG from ddtrace.contrib.asgi import TraceMiddleware from ddtrace.contrib.asgi import span_from_scope -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES from ddtrace.propagation import http as http_propagation +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import DummyTracer from tests.utils import override_http_config @@ -234,13 +234,16 @@ async def test(scope, tracer, test_spans): assert err == b"", f"STDOUT\n{out.decode()}\nSTDERR\n{err.decode()}" -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) -@pytest.mark.parametrize("global_service_name", [None, "mysvc"]) +@pytest.mark.parametrize( + "schema_version, global_service_name", + [(None, None), (None, "mysvc"), ("v0", None), ("v0", "mysvc"), ("v1", None), ("v1", "mysvc")], +) def test_span_attribute_schema_service_name(ddtrace_run_python_code_in_subprocess, schema_version, global_service_name): + inferred_base_service = DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME expected_service_name = { - None: global_service_name or "", - "v0": global_service_name or "", - "v1": global_service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + None: global_service_name or inferred_base_service, + "v0": global_service_name or inferred_base_service, + "v1": global_service_name or inferred_base_service, }[schema_version] code = """ import pytest diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index 81ec2f5e737..04a04268099 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -30,11 +30,11 @@ from ddtrace.ext import http from ddtrace.ext import user from ddtrace.internal.compat import ensure_text -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES from ddtrace.propagation._utils import get_wsgi_header from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.opentracer.utils import init_tracer from tests.utils import assert_dict_issuperset from tests.utils import flaky @@ -1497,11 +1497,13 @@ def test_service_can_be_overridden(client, test_spans): @pytest.mark.parametrize("global_service_name", [None, "mysvc"]) @pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) -def test_schematized_default_service_name(ddtrace_run_python_code_in_subprocess, schema_version, global_service_name): +def test_schematized_default_service_name( + ddtrace_run_python_code_in_subprocess, schema_version, global_service_name, request +): expected_service_name = { None: global_service_name or "django", "v0": global_service_name or "django", - "v1": global_service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + "v1": global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ import pytest @@ -1540,15 +1542,17 @@ def test(client, test_spans): assert status == 0, (out, err) -@pytest.mark.parametrize("global_service_name", [None, "mysvc"]) -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) +@pytest.mark.parametrize( + "schema_version, global_service_name", + [(None, None), (None, "mysvc"), ("v0", None), ("v0", "mysvc"), ("v1", None), ("v1", "mysvc")], +) def test_schematized_default_db_service_name( - ddtrace_run_python_code_in_subprocess, schema_version, global_service_name + ddtrace_run_python_code_in_subprocess, schema_version, global_service_name, request ): expected_service_name = { None: "defaultdb", "v0": "defaultdb", - "v1": global_service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + "v1": global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ import pytest diff --git a/tests/contrib/djangorestframework/test_djangorestframework.py b/tests/contrib/djangorestframework/test_djangorestframework.py index eee2fa7cc15..8afd158f51b 100644 --- a/tests/contrib/djangorestframework/test_djangorestframework.py +++ b/tests/contrib/djangorestframework/test_djangorestframework.py @@ -4,7 +4,7 @@ import pytest from ddtrace.constants import ERROR_MSG -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import assert_span_http_status_code @@ -44,7 +44,9 @@ def test_trace_exceptions(client, test_spans): # noqa flake8 complains about sh @pytest.mark.django_db @pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) def test_schematized_service_names(ddtrace_run_python_code_in_subprocess, schema_version): - expected_service_name = {None: "django", "v0": "django", "v1": _DEFAULT_SPAN_SERVICE_NAMES["v1"]}[schema_version] + expected_service_name = {None: "django", "v0": "django", "v1": DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME}[ + schema_version + ] code = """ import pytest import sys diff --git a/tests/contrib/dogpile_cache/test_tracing.py b/tests/contrib/dogpile_cache/test_tracing.py index 5967caedcac..fbf10ab4a83 100644 --- a/tests/contrib/dogpile_cache/test_tracing.py +++ b/tests/contrib/dogpile_cache/test_tracing.py @@ -6,7 +6,7 @@ from ddtrace import Pin from ddtrace.contrib.dogpile_cache.patch import patch from ddtrace.contrib.dogpile_cache.patch import unpatch -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import DummyTracer from tests.utils import TracerSpanContainer from tests.utils import assert_is_measured @@ -232,9 +232,9 @@ def test_get_or_create_kwarg_only(region): @pytest.mark.parametrize( "schema_tuples", [ - (None, None, None, "dogpile.cache"), - (None, "v0", None, "dogpile.cache"), - (None, "v1", _DEFAULT_SPAN_SERVICE_NAMES["v1"], "dogpile.command"), + (None, None, DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "dogpile.cache"), + (None, "v0", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "dogpile.cache"), + (None, "v1", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "dogpile.command"), ("mysvc", None, "mysvc", "dogpile.cache"), ("mysvc", "v0", "mysvc", "dogpile.cache"), ("mysvc", "v1", "mysvc", "dogpile.command"), diff --git a/tests/contrib/elasticsearch/test_async.py b/tests/contrib/elasticsearch/test_async.py index ebc5f82c00b..09276ac5e27 100644 --- a/tests/contrib/elasticsearch/test_async.py +++ b/tests/contrib/elasticsearch/test_async.py @@ -2,6 +2,7 @@ import subprocess import sys +from tests.conftest import create_ddtrace_subprocess_dir_and_return_test_pyfile from tests.utils import snapshot @@ -38,7 +39,7 @@ async def main(): def do_test(tmpdir, es_module, async_class): - f = tmpdir.join("test.py") + f = create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir) f.write(code % {"module": es_module, "class": async_class}) env = os.environ.copy() # ddtrace-run patches sqlite3 which is used by coverage to store coverage @@ -51,7 +52,7 @@ def do_test(tmpdir, es_module, async_class): } ) p = subprocess.Popen( - ["ddtrace-run", sys.executable, "test.py"], + ["ddtrace-run", sys.executable, str(f)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=str(tmpdir), diff --git a/tests/contrib/elasticsearch/test_elasticsearch_multi.py b/tests/contrib/elasticsearch/test_elasticsearch_multi.py index 6b1fbccbdb7..90b6d611397 100644 --- a/tests/contrib/elasticsearch/test_elasticsearch_multi.py +++ b/tests/contrib/elasticsearch/test_elasticsearch_multi.py @@ -2,6 +2,7 @@ import subprocess import sys +from tests.conftest import create_ddtrace_subprocess_dir_and_return_test_pyfile from tests.utils import snapshot @@ -30,7 +31,7 @@ def do_test(tmpdir, es_version): - f = tmpdir.join("test.py") + f = create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir) f.write(code % es_version) env = os.environ.copy() # ddtrace-run patches sqlite3 which is used by coverage to store coverage @@ -38,7 +39,7 @@ def do_test(tmpdir, es_version): # with the snapshot. So disable sqlite3. env.update({"DD_TRACE_SQLITE3_ENABLED": "false"}) p = subprocess.Popen( - ["ddtrace-run", sys.executable, "test.py"], + ["ddtrace-run", sys.executable, str(f)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=str(tmpdir), diff --git a/tests/contrib/falcon/test_schematization.py b/tests/contrib/falcon/test_schematization.py index 13c793b8ba8..5cfd4e71d2e 100644 --- a/tests/contrib/falcon/test_schematization.py +++ b/tests/contrib/falcon/test_schematization.py @@ -2,12 +2,14 @@ import pytest -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME @pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) def test_schematized_service_name(ddtrace_run_python_code_in_subprocess, schema_version): - expected_service_name = {None: "falcon", "v0": "falcon", "v1": _DEFAULT_SPAN_SERVICE_NAMES["v1"]}[schema_version] + expected_service_name = {None: "falcon", "v0": "falcon", "v1": DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME}[ + schema_version + ] code = """ import pytest import falcon diff --git a/tests/contrib/fastapi/test_fastapi.py b/tests/contrib/fastapi/test_fastapi.py index 3daadd055ec..f4adc720f8b 100644 --- a/tests/contrib/fastapi/test_fastapi.py +++ b/tests/contrib/fastapi/test_fastapi.py @@ -8,9 +8,9 @@ from ddtrace.contrib.starlette.patch import patch as patch_starlette from ddtrace.contrib.starlette.patch import unpatch as unpatch_starlette -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES from ddtrace.internal.utils.version import parse_version from ddtrace.propagation import http as http_propagation +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import flaky from tests.utils import override_config from tests.utils import override_http_config @@ -614,7 +614,7 @@ def test_tracing_in_middleware(snapshot_app_with_middleware): [ (None, None, "fastapi", "fastapi.request"), (None, "v0", "fastapi", "fastapi.request"), - (None, "v1", _DEFAULT_SPAN_SERVICE_NAMES["v1"], "http.server.request"), + (None, "v1", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, "http.server.request"), ("mysvc", None, "mysvc", "fastapi.request"), ("mysvc", "v0", "mysvc", "fastapi.request"), ("mysvc", "v1", "mysvc", "http.server.request"), diff --git a/tests/contrib/flask/test_request.py b/tests/contrib/flask/test_request.py index 5e1ef189f31..62df061a198 100644 --- a/tests/contrib/flask/test_request.py +++ b/tests/contrib/flask/test_request.py @@ -14,9 +14,9 @@ from ddtrace.constants import ERROR_MSG from ddtrace.contrib.flask.patch import flask_version from ddtrace.ext import http -from ddtrace.internal.schema import _DEFAULT_SPAN_SERVICE_NAMES from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -965,8 +965,10 @@ def hello(): assert traces[0][-1].name == "hello_2" -@pytest.mark.parametrize("service_name", [None, "mysvc"]) -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) +@pytest.mark.parametrize( + "schema_version, service_name", + [(None, None), (None, "mysvc"), ("v0", None), ("v0", "mysvc"), ("v1", None), ("v1", "mysvc")], +) def test_schematized_service_name(ddtrace_run_python_code_in_subprocess, schema_version, service_name): """ v0/Default: expect the service name to be "flask" @@ -976,7 +978,7 @@ def test_schematized_service_name(ddtrace_run_python_code_in_subprocess, schema_ expected_service_name = { None: service_name or "flask", "v0": service_name or "flask", - "v1": service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + "v1": service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ diff --git a/tests/contrib/graphql/test_graphql.py b/tests/contrib/graphql/test_graphql.py index a3c30e48567..ce4620bc70d 100644 --- a/tests/contrib/graphql/test_graphql.py +++ b/tests/contrib/graphql/test_graphql.py @@ -160,8 +160,10 @@ def test_graphql_execute_sync_with_middlware_manager( @pytest.mark.snapshot @pytest.mark.skipif(graphql_version < (3, 0), reason="graphql.graphql_sync is NOT suppoerted in v2.0") -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) -@pytest.mark.parametrize("service_name", [None, "my-service"]) +@pytest.mark.parametrize( + "service_name, schema_version", + [(None, None), ("my-service", None), (None, "v0"), ("my-service", "v0"), (None, "v1"), ("my-service", "v1")], +) def test_span_schematization(ddtrace_run_python_code_in_subprocess, schema_version, service_name): code = """ import sys diff --git a/tests/contrib/openai/test_openai_v0.py b/tests/contrib/openai/test_openai_v0.py index 2878bb8cdf6..1bbb8400179 100644 --- a/tests/contrib/openai/test_openai_v0.py +++ b/tests/contrib/openai/test_openai_v0.py @@ -1489,7 +1489,7 @@ def test_integration_sync(openai_api_key, ddtrace_run_python_code_in_subprocess) @pytest.mark.snapshot( - token="tests.contrib.openai.test_openai.test_acompletion", + token="tests.contrib.openai.test_openai.test_integration_async", ignores=[ "meta.http.useragent", "meta.openai.base_url", diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index d6b44203f43..c6598e4d9e5 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -1290,7 +1290,7 @@ def test_integration_sync(openai_api_key, ddtrace_run_python_code_in_subprocess) @pytest.mark.snapshot( - token="tests.contrib.openai.test_openai_v1.test_integration_sync", + token="tests.contrib.openai.test_openai_v1.test_integration_async", ignores=["meta.http.useragent"], async_mode=False, ) diff --git a/tests/contrib/psycopg/test_psycopg_snapshot.py b/tests/contrib/psycopg/test_psycopg_snapshot.py index 86571b48abc..00b0a679991 100644 --- a/tests/contrib/psycopg/test_psycopg_snapshot.py +++ b/tests/contrib/psycopg/test_psycopg_snapshot.py @@ -7,8 +7,6 @@ from ddtrace.contrib.psycopg.patch import patch from ddtrace.contrib.psycopg.patch import unpatch -from tests.contrib.config import POSTGRES_CONFIG -from tests.utils import override_config @pytest.fixture(autouse=True) @@ -36,22 +34,35 @@ def patch_psycopg(): unpatch() -@pytest.mark.snapshot() +@pytest.mark.subprocess(ddtrace_run=True) +@pytest.mark.snapshot(wait_for_num_traces=0) def test_connect_default(): """By default we do not trace psycopg.connect method""" + import psycopg + + from tests.contrib.config import POSTGRES_CONFIG + conn = psycopg.connect(**POSTGRES_CONFIG) assert conn +@pytest.mark.subprocess(ddtrace_run=True) @pytest.mark.snapshot(wait_for_num_traces=1) def test_connect_traced(): """When explicitly enabled, we trace psycopg.connect method""" + import psycopg + + from tests.contrib.config import POSTGRES_CONFIG + from tests.utils import override_config + with override_config("psycopg", {"trace_connect": True}): conn = psycopg.connect(**POSTGRES_CONFIG) assert conn -@pytest.mark.snapshot(token="tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced", wait_for_num_traces=1) +@pytest.mark.snapshot( + token="tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced_via_env", wait_for_num_traces=1 +) def test_connect_traced_via_env(run_python_code_in_subprocess): """When explicitly enabled, we trace psycopg.connect method""" diff --git a/tests/contrib/psycopg2/test_psycopg_snapshot.py b/tests/contrib/psycopg2/test_psycopg_snapshot.py index c2e63464c2a..c3210077598 100644 --- a/tests/contrib/psycopg2/test_psycopg_snapshot.py +++ b/tests/contrib/psycopg2/test_psycopg_snapshot.py @@ -6,8 +6,6 @@ from ddtrace.contrib.psycopg.patch import patch from ddtrace.contrib.psycopg.patch import unpatch -from tests.contrib.config import POSTGRES_CONFIG -from tests.utils import override_config @pytest.fixture(autouse=True) @@ -18,22 +16,35 @@ def patch_psycopg(): unpatch() -@pytest.mark.snapshot() +@pytest.mark.subprocess(ddtrace_run=True) +@pytest.mark.snapshot(wait_for_num_traces=0) def test_connect_default(): """By default we do not trace psycopg2.connect method""" + import psycopg2 + + from tests.contrib.config import POSTGRES_CONFIG + conn = psycopg2.connect(**POSTGRES_CONFIG) assert conn +@pytest.mark.subprocess(ddtrace_run=True) @pytest.mark.snapshot(wait_for_num_traces=1) def test_connect_traced(): """When explicitly enabled, we trace psycopg2.connect method""" + import psycopg2 + + from tests.contrib.config import POSTGRES_CONFIG + from tests.utils import override_config + with override_config("psycopg", {"trace_connect": True}): conn = psycopg2.connect(**POSTGRES_CONFIG) assert conn -@pytest.mark.snapshot(token="tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced", wait_for_num_traces=1) +@pytest.mark.snapshot( + token="tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced_via_env", wait_for_num_traces=1 +) def test_connect_traced_via_env(run_python_code_in_subprocess): """When explicitly enabled, we trace psycopg2.connect method""" diff --git a/tests/contrib/sanic/test_sanic.py b/tests/contrib/sanic/test_sanic.py index d4cbd5a5b50..9ae6c349d8e 100644 --- a/tests/contrib/sanic/test_sanic.py +++ b/tests/contrib/sanic/test_sanic.py @@ -17,8 +17,8 @@ from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES from ddtrace.propagation import http as http_propagation +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import override_config from tests.utils import override_http_config @@ -427,7 +427,7 @@ def test_service_name_schematization(ddtrace_run_python_code_in_subprocess, sche expected_service_name = { None: service_name or "sanic", "v0": service_name or "sanic", - "v1": service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + "v1": service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ import asyncio diff --git a/tests/contrib/sanic/test_sanic_server.py b/tests/contrib/sanic/test_sanic_server.py index 3bcf7be807b..bf570db03d0 100644 --- a/tests/contrib/sanic/test_sanic_server.py +++ b/tests/contrib/sanic/test_sanic_server.py @@ -40,7 +40,7 @@ def sanic_client(): @pytest.mark.snapshot( - ignores=["meta.http.useragent"], + ignores=["meta.http.useragent", "meta._dd.base_service"], variants={ "": sanic_version >= (21, 9, 0), "pre_21.9": sanic_version < (21, 9, 0), @@ -57,7 +57,7 @@ def assert_response(response): @pytest.mark.snapshot( - ignores=["meta.error.stack", "meta.http.useragent"], + ignores=["meta.error.stack", "meta.http.useragent", "meta._dd.base_service"], variants={ "": sanic_version >= (21, 9, 0), "pre_21.9": sanic_version < (21, 9, 0), diff --git a/tests/contrib/wsgi/test_wsgi.py b/tests/contrib/wsgi/test_wsgi.py index 6db2c9e43f5..99f4d4b9a0b 100644 --- a/tests/contrib/wsgi/test_wsgi.py +++ b/tests/contrib/wsgi/test_wsgi.py @@ -390,9 +390,11 @@ def test_get_request_headers(extra, expected): @pytest.mark.snapshot() -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) -@pytest.mark.parametrize("service_name", [None, "mysvc"]) -def test_schematization(ddtrace_run_python_code_in_subprocess, schema_version, service_name): +@pytest.mark.parametrize( + "service_name, schema_version", + [(None, None), ("mysvc", None), (None, "v0"), ("mysvc", "v0"), (None, "v1"), ("mysvc", "v1")], +) +def test_schematization(ddtrace_run_python_code_in_subprocess, service_name, schema_version): code = """ from webtest import TestApp from ddtrace.contrib.wsgi import DDWSGIMiddleware diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 5a7ab1537e6..dea74979968 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -763,7 +763,7 @@ def test_logging_during_tracer_init_succeeds_when_debug_logging_and_logs_injecti assert out == b"", "an empty program should generate no logs under ddtrace-run" assert ( - b"[dd.service= dd.env= dd.version= dd.trace_id=0 dd.span_id=0]" in err + b"[dd.service=ddtrace_subprocess_dir dd.env= dd.version= dd.trace_id=0 dd.span_id=0]" in err ), "stderr should contain debug output when DD_TRACE_DEBUG is set" assert b"KeyError: 'dd.service'" not in err, "stderr should not contain any exception logs" diff --git a/tests/internal/service_name/test_imports.py b/tests/internal/service_name/test_imports.py index 92537cbe320..b52b8389599 100644 --- a/tests/internal/service_name/test_imports.py +++ b/tests/internal/service_name/test_imports.py @@ -14,8 +14,9 @@ def test_service_names_import_default(): from ddtrace.internal.schema.span_attribute_schema import database_operation_v0 from ddtrace.internal.schema.span_attribute_schema import service_name_v0 from ddtrace.internal.schema.span_attribute_schema import url_operation_v0 + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME - assert DEFAULT_SPAN_SERVICE_NAME is None + assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME assert schematize_service_name == service_name_v0 assert schematize_database_operation == database_operation_v0 assert schematize_cache_operation == cache_operation_v0 @@ -36,8 +37,9 @@ def test_service_names_import_and_v0(): from ddtrace.internal.schema.span_attribute_schema import database_operation_v0 from ddtrace.internal.schema.span_attribute_schema import service_name_v0 from ddtrace.internal.schema.span_attribute_schema import url_operation_v0 + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME - assert DEFAULT_SPAN_SERVICE_NAME is None + assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME assert schematize_service_name == service_name_v0 assert schematize_database_operation == database_operation_v0 assert schematize_cache_operation == cache_operation_v0 @@ -50,7 +52,6 @@ def test_service_names_import_and_v0(): parametrize={"DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED": ["False", "True"]}, ) def test_service_name_imports_v1(): - from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.internal.schema import schematize_cache_operation from ddtrace.internal.schema import schematize_cloud_api_operation @@ -62,8 +63,9 @@ def test_service_name_imports_v1(): from ddtrace.internal.schema.span_attribute_schema import database_operation_v1 from ddtrace.internal.schema.span_attribute_schema import service_name_v1 from ddtrace.internal.schema.span_attribute_schema import url_operation_v1 + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME - assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_SERVICE_NAME + assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME assert schematize_service_name == service_name_v1 assert schematize_database_operation == database_operation_v1 assert schematize_cache_operation == cache_operation_v1 @@ -78,7 +80,6 @@ def test_service_name_import_with_client_service_names_enabled_v0(): """ Service name parameters are flipped when DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED is True for v0 """ - from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.internal.schema import schematize_cache_operation from ddtrace.internal.schema import schematize_cloud_api_operation @@ -90,8 +91,9 @@ def test_service_name_import_with_client_service_names_enabled_v0(): from ddtrace.internal.schema.span_attribute_schema import database_operation_v0 from ddtrace.internal.schema.span_attribute_schema import service_name_v1 from ddtrace.internal.schema.span_attribute_schema import url_operation_v0 + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME - assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_SERVICE_NAME + assert DEFAULT_SPAN_SERVICE_NAME == DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME assert schematize_service_name == service_name_v1 assert schematize_database_operation == database_operation_v0 assert schematize_cache_operation == cache_operation_v0 diff --git a/tests/internal/service_name/test_inferred_base_service.py b/tests/internal/service_name/test_inferred_base_service.py new file mode 100644 index 00000000000..fa79d3ad785 --- /dev/null +++ b/tests/internal/service_name/test_inferred_base_service.py @@ -0,0 +1,80 @@ +import pathlib +import tempfile +from unittest.mock import patch + +import pytest + +from ddtrace.settings._inferred_base_service import detect_service + + +@pytest.fixture +def mock_file_system(): + """Setup a mock filesystem.""" + # Use a temporary directory for testing. + with tempfile.TemporaryDirectory() as temp_dir: + base_path = pathlib.Path(temp_dir) + base_path.mkdir(exist_ok=True) + + # Create the mock directory structure + (base_path / "__pycache__").mkdir(parents=True) + (base_path / "venv" / "bin" / "python3.11").mkdir(parents=True) + (base_path / "venv" / "bin" / "gunicorn").mkdir(parents=True) + + (base_path / "modules" / "m1" / "first" / "nice" / "package").mkdir(parents=True) + (base_path / "modules" / "m2").mkdir(parents=True) + (base_path / "modules" / "no" / "python_files").mkdir(parents=True) + (base_path / "apps" / "app1").mkdir(parents=True) + (base_path / "apps" / "app2" / "cmd").mkdir(parents=True) + + # Create Python and other files + (base_path / "__pycache__" / "app.cpython-311.pyc").touch() + (base_path / "venv" / "bin" / "python3.11" / "ddtrace" / "__init__.py").mkdir(parents=True) + (base_path / "venv" / "bin" / "python3.11" / "gunicorn" / "__init__.py").mkdir(parents=True) + (base_path / "venv" / "bin" / "gunicorn" / "__init__.py").touch() + + (base_path / "modules" / "m1" / "first" / "nice" / "package" / "__init__.py").touch() + (base_path / "modules" / "m1" / "first" / "nice" / "__init__.py").touch() + (base_path / "modules" / "m1" / "first" / "nice" / "something.py").touch() + (base_path / "modules" / "m1" / "first" / "__init__.py").touch() + (base_path / "modules" / "m1" / "__init__.py").touch() + (base_path / "apps" / "app1" / "__main__.py").touch() + (base_path / "apps" / "app2" / "cmd" / "run.py").touch() + (base_path / "apps" / "app2" / "setup.py").touch() + + # Additional edge cases + (base_path / "modules" / "no" / "python_files" / "here.txt").touch() # Module with no subdirectories + (base_path / "modules" / "m1" / "first" / "nice" / "package" / "not_a_python_file.txt").touch() + + yield base_path + + +@pytest.mark.parametrize( + "cmd,expected", + [ + ("python modules/m1/first/nice/package", "m1.first.nice.package"), + ("python modules/m1/first/nice", "m1.first.nice"), + ("python modules/m1/first/nice/something.py", "m1.first.nice"), + ("python modules/m1/first", "m1.first"), + ("python modules/m2", "m2"), + ("python apps/app1", "app1"), + ("python apps/app2/cmd/run.py", "app2"), + ("python apps/app2/setup.py", "app2"), + ("DD_ENV=prod OTHER_ENV_VAR=hi python apps/app2/setup.py", "app2"), + ("python3.7 apps/app2/setup.py", "app2"), + ("/usr/bin/python3.11 apps/app2/setup.py", "app2"), + # Additional Python test cases + ("venv/bin/python3.11/ddtrace-run venv/bin/python3.11 apps/app2/setup.py", "app2"), + ("venv/bin/python3.11/ddtrace-run python apps/app2/setup.py", "app2"), + ("ddtrace-run python apps/app2/setup.py", "app2"), + ("python3.12 apps/app2/cmd/run.py", "app2"), + ("python -m m1.first.nice.package", "m1.first.nice.package"), + ("python -m http.server 8000", "http.server"), + ], +) +def test_python_detector(cmd, expected, mock_file_system): + # Mock the current working directory to the test_modules path + with patch("os.getcwd", return_value=str(mock_file_system)): + cmd_parts = cmd.split(" ") + detected_name = detect_service(cmd_parts) + + assert detected_name == expected, f"Test failed for command: [{cmd}]" diff --git a/tests/internal/service_name/test_processor.py b/tests/internal/service_name/test_processor.py index 5aacba769ba..02e238b8955 100644 --- a/tests/internal/service_name/test_processor.py +++ b/tests/internal/service_name/test_processor.py @@ -3,7 +3,7 @@ import pytest from ddtrace.internal.schema.processor import BaseServiceProcessor -from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME @pytest.fixture @@ -15,9 +15,9 @@ def processor(): @pytest.mark.parametrize("global_service_name", [None, "mysvc"]) def test_base_service(ddtrace_run_python_code_in_subprocess, schema_version, global_service_name): expected_base_service_name = { - None: global_service_name or "", - "v0": global_service_name or "", - "v1": global_service_name or _DEFAULT_SPAN_SERVICE_NAMES["v1"], + None: global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, + "v0": global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, + "v1": global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json index fc182f0ce3c..ebce28ebf48 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -80,7 +80,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -95,7 +95,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -152,7 +152,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -167,7 +167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -224,7 +224,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -239,7 +239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -296,7 +296,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -311,7 +311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -368,7 +368,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -383,7 +383,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -440,7 +440,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -455,7 +455,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -512,7 +512,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -527,7 +527,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -584,7 +584,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -599,7 +599,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -652,7 +652,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -667,7 +667,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -721,7 +721,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -736,7 +736,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -790,7 +790,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -805,7 +805,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -859,7 +859,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -874,7 +874,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -928,7 +928,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -943,7 +943,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -997,7 +997,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1012,7 +1012,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1066,7 +1066,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1081,7 +1081,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1135,7 +1135,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1150,7 +1150,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1207,7 +1207,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1222,7 +1222,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1276,7 +1276,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1291,7 +1291,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1345,7 +1345,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1360,7 +1360,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1415,7 +1415,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1430,7 +1430,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1485,7 +1485,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1500,7 +1500,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1555,7 +1555,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1570,7 +1570,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1625,7 +1625,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1640,7 +1640,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1695,7 +1695,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1710,7 +1710,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1764,7 +1764,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1779,7 +1779,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1827,7 +1827,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1842,7 +1842,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1890,7 +1890,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1905,7 +1905,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1953,7 +1953,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1968,7 +1968,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2016,7 +2016,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2031,7 +2031,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2079,7 +2079,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2094,7 +2094,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2143,7 +2143,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2158,7 +2158,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2211,7 +2211,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2226,7 +2226,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2279,7 +2279,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2294,7 +2294,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2347,7 +2347,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2362,7 +2362,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2415,7 +2415,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2430,7 +2430,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2483,7 +2483,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2498,7 +2498,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2551,7 +2551,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2566,7 +2566,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2619,7 +2619,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2634,7 +2634,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2687,7 +2687,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2702,7 +2702,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2755,7 +2755,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2770,7 +2770,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2826,7 +2826,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2841,7 +2841,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2894,7 +2894,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2909,7 +2909,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2963,7 +2963,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2978,7 +2978,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json index 2e6be6d87a0..3bd4c8bc048 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -80,7 +80,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -95,7 +95,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -152,7 +152,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -167,7 +167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -224,7 +224,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -239,7 +239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -296,7 +296,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -311,7 +311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -368,7 +368,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -383,7 +383,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -436,7 +436,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -451,7 +451,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -505,7 +505,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -520,7 +520,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -574,7 +574,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -589,7 +589,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -646,7 +646,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -661,7 +661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -715,7 +715,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -730,7 +730,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -784,7 +784,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -799,7 +799,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -847,7 +847,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -862,7 +862,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -910,7 +910,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -925,7 +925,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -973,7 +973,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -988,7 +988,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1036,7 +1036,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1051,7 +1051,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1100,7 +1100,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1115,7 +1115,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1171,7 +1171,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1186,7 +1186,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1239,7 +1239,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1254,7 +1254,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1308,7 +1308,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1323,7 +1323,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1377,7 +1377,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1392,7 +1392,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1446,7 +1446,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1461,7 +1461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1515,7 +1515,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1530,7 +1530,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1584,7 +1584,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1599,7 +1599,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/test_manual_api_fake_atr_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json index a81d333e1d8..566f49d6e36 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -82,7 +82,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -97,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -155,7 +155,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -170,7 +170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -228,7 +228,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -243,7 +243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -301,7 +301,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -316,7 +316,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -374,7 +374,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -389,7 +389,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -447,7 +447,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -462,7 +462,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -520,7 +520,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -535,7 +535,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -593,7 +593,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -608,7 +608,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -666,7 +666,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -681,7 +681,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -739,7 +739,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -754,7 +754,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -811,7 +811,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -881,7 +881,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -896,7 +896,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -951,7 +951,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -966,7 +966,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1021,7 +1021,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1036,7 +1036,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1091,7 +1091,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1106,7 +1106,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1161,7 +1161,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1176,7 +1176,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1230,7 +1230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1302,7 +1302,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1372,7 +1372,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1387,7 +1387,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1442,7 +1442,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1457,7 +1457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1506,7 +1506,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1521,7 +1521,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1569,7 +1569,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1584,7 +1584,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1632,7 +1632,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1647,7 +1647,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1695,7 +1695,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1710,7 +1710,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1758,7 +1758,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1773,7 +1773,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1822,7 +1822,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1837,7 +1837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1890,7 +1890,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1905,7 +1905,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1958,7 +1958,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1973,7 +1973,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2026,7 +2026,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2041,7 +2041,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2094,7 +2094,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2109,7 +2109,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2162,7 +2162,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2177,7 +2177,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2230,7 +2230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2245,7 +2245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2298,7 +2298,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2313,7 +2313,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2366,7 +2366,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2381,7 +2381,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2434,7 +2434,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2449,7 +2449,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2505,7 +2505,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2520,7 +2520,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2575,7 +2575,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2590,7 +2590,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2648,7 +2648,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2663,7 +2663,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2716,7 +2716,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2731,7 +2731,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json index 602d3dd4ca8..a3cb66658d9 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -81,7 +81,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -96,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -150,7 +150,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -165,7 +165,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -222,7 +222,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -237,7 +237,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -292,7 +292,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -307,7 +307,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -362,7 +362,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -377,7 +377,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -427,7 +427,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -442,7 +442,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -490,7 +490,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -505,7 +505,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -553,7 +553,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -568,7 +568,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -616,7 +616,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -631,7 +631,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -679,7 +679,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -694,7 +694,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -743,7 +743,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -758,7 +758,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -812,7 +812,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -827,7 +827,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -881,7 +881,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -896,7 +896,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -950,7 +950,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -965,7 +965,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1019,7 +1019,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1034,7 +1034,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1088,7 +1088,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1103,7 +1103,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1157,7 +1157,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1172,7 +1172,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1226,7 +1226,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1241,7 +1241,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1295,7 +1295,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1310,7 +1310,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1364,7 +1364,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1379,7 +1379,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1435,7 +1435,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1450,7 +1450,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1504,7 +1504,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1519,7 +1519,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1577,7 +1577,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1592,7 +1592,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1646,7 +1646,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1661,7 +1661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json index 02c650a2341..9ec2386f182 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -82,7 +82,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -97,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -155,7 +155,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -170,7 +170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -228,7 +228,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -243,7 +243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -301,7 +301,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -316,7 +316,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -374,7 +374,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -389,7 +389,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -447,7 +447,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -462,7 +462,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -520,7 +520,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -535,7 +535,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -593,7 +593,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -608,7 +608,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -666,7 +666,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -681,7 +681,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -739,7 +739,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -754,7 +754,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -811,7 +811,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -881,7 +881,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -896,7 +896,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -951,7 +951,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -966,7 +966,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1021,7 +1021,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1036,7 +1036,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1091,7 +1091,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1106,7 +1106,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1161,7 +1161,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1176,7 +1176,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1230,7 +1230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1302,7 +1302,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1372,7 +1372,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1387,7 +1387,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1442,7 +1442,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1457,7 +1457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1506,7 +1506,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1521,7 +1521,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1569,7 +1569,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1584,7 +1584,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1632,7 +1632,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1647,7 +1647,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1695,7 +1695,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1710,7 +1710,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1758,7 +1758,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1773,7 +1773,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1822,7 +1822,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1837,7 +1837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1890,7 +1890,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1905,7 +1905,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1958,7 +1958,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1973,7 +1973,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2026,7 +2026,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2041,7 +2041,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2094,7 +2094,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2109,7 +2109,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2162,7 +2162,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2177,7 +2177,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2230,7 +2230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2245,7 +2245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2298,7 +2298,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2313,7 +2313,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2366,7 +2366,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2381,7 +2381,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2434,7 +2434,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2449,7 +2449,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2505,7 +2505,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2520,7 +2520,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2575,7 +2575,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2590,7 +2590,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2648,7 +2648,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2663,7 +2663,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2716,7 +2716,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2731,7 +2731,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json index 823b3ca3017..462dbdac481 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -82,7 +82,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -97,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -155,7 +155,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -170,7 +170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -228,7 +228,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -243,7 +243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -301,7 +301,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -316,7 +316,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -374,7 +374,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -389,7 +389,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -447,7 +447,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -462,7 +462,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -520,7 +520,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -535,7 +535,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -593,7 +593,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -608,7 +608,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -666,7 +666,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -681,7 +681,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -739,7 +739,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -754,7 +754,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -811,7 +811,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -881,7 +881,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -896,7 +896,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -951,7 +951,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -966,7 +966,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1021,7 +1021,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1036,7 +1036,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1091,7 +1091,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1106,7 +1106,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1161,7 +1161,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1176,7 +1176,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1230,7 +1230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1302,7 +1302,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1372,7 +1372,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1387,7 +1387,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1442,7 +1442,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1457,7 +1457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1506,7 +1506,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1521,7 +1521,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1569,7 +1569,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1584,7 +1584,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1632,7 +1632,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1647,7 +1647,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1695,7 +1695,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1710,7 +1710,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1758,7 +1758,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1773,7 +1773,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1822,7 +1822,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1837,7 +1837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1890,7 +1890,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1905,7 +1905,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1958,7 +1958,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1973,7 +1973,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2026,7 +2026,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2041,7 +2041,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2094,7 +2094,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2109,7 +2109,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2162,7 +2162,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2177,7 +2177,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2230,7 +2230,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2245,7 +2245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2298,7 +2298,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2313,7 +2313,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2366,7 +2366,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2381,7 +2381,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2434,7 +2434,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2449,7 +2449,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2505,7 +2505,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2520,7 +2520,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2575,7 +2575,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2590,7 +2590,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2648,7 +2648,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2663,7 +2663,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2716,7 +2716,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2731,7 +2731,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json index 0ceaa864bfd..b94cc7a2a02 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json @@ -9,14 +9,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -64,14 +64,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -116,14 +116,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -172,11 +172,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -216,11 +216,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -260,11 +260,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -304,11 +304,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -348,11 +348,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -393,14 +393,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -448,14 +448,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -500,14 +500,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json index 059ed905da8..ddf2efb62b7 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -65,11 +65,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -118,11 +118,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -175,12 +175,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -226,12 +226,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -277,11 +277,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -327,12 +327,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -378,11 +378,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -429,11 +429,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -485,11 +485,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -538,11 +538,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/test_manual_api_fake_runner_all_itr_skip_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json index 0867a2d8704..db965dd97cb 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -65,11 +65,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -118,11 +118,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -175,12 +175,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -226,12 +226,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -277,11 +277,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -326,12 +326,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -377,11 +377,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -427,11 +427,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -483,11 +483,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", @@ -536,11 +536,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/test_manual_api_fake_runner_all_itr_skip_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json index 5f12010b3fd..f2229ef33b4 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -80,7 +80,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -95,7 +95,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -148,7 +148,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -163,7 +163,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -220,7 +220,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -235,7 +235,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -283,7 +283,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -298,7 +298,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -346,7 +346,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -361,7 +361,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -409,7 +409,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -424,7 +424,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -472,7 +472,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -487,7 +487,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -536,7 +536,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -551,7 +551,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -607,7 +607,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -622,7 +622,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -675,7 +675,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -690,7 +690,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json index 6153d89ed03..2451fa700b5 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -62,11 +62,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -112,11 +112,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -166,11 +166,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -210,11 +210,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -254,11 +254,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -298,11 +298,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -342,11 +342,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -387,11 +387,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -440,11 +440,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -490,11 +490,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_all_skip0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json index 65f7fd735ed..242e7a331d9 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -61,11 +61,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -111,11 +111,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -164,11 +164,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -217,11 +217,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -267,14 +267,14 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0/fake_runner_mix_fail.py\", line 22, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_mix_fail.py\", line 22, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -322,11 +322,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -366,11 +366,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -410,11 +410,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -454,11 +454,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -498,11 +498,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -542,11 +542,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -586,11 +586,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -630,11 +630,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -675,11 +675,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -727,11 +727,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -783,11 +783,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -836,11 +836,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -889,11 +889,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -942,11 +942,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -995,11 +995,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1051,11 +1051,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1103,11 +1103,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1155,11 +1155,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1207,11 +1207,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1259,11 +1259,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -1311,11 +1311,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json index 5f8dbae2b41..bb1f78251c7 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -83,7 +83,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -98,7 +98,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -155,7 +155,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -170,7 +170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -231,7 +231,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -246,7 +246,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -306,7 +306,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -321,7 +321,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -378,7 +378,7 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -393,10 +393,10 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0/fake_runner_mix_fail_itr_suite_level.py\", line 35, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir/fake_runner_mix_fail_itr_suite_level.py\", line 35, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -455,7 +455,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -471,7 +471,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -526,7 +526,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", @@ -542,7 +542,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -596,7 +596,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -611,7 +611,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -664,7 +664,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", @@ -680,7 +680,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -734,7 +734,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -749,7 +749,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -802,7 +802,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", @@ -818,7 +818,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -872,7 +872,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -887,7 +887,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -940,7 +940,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -955,7 +955,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1008,7 +1008,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -1024,7 +1024,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1078,7 +1078,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1093,7 +1093,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1147,7 +1147,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1162,7 +1162,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1216,7 +1216,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1231,7 +1231,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1290,7 +1290,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1305,7 +1305,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1368,7 +1368,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1383,7 +1383,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1443,7 +1443,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1458,7 +1458,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1519,7 +1519,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1534,7 +1534,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1594,7 +1594,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1609,7 +1609,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1669,7 +1669,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1684,7 +1684,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1747,7 +1747,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1762,7 +1762,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1821,7 +1821,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1836,7 +1836,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1895,7 +1895,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1910,7 +1910,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1969,7 +1969,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1984,7 +1984,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2043,7 +2043,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2058,7 +2058,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2117,7 +2117,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2132,7 +2132,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2191,7 +2191,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2206,7 +2206,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2266,7 +2266,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2281,7 +2281,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2341,7 +2341,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2356,7 +2356,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2416,7 +2416,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2431,7 +2431,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2488,7 +2488,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2503,7 +2503,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2559,7 +2559,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2574,7 +2574,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/test_manual_api_fake_runner_mix_fail_itr_suite_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json index 194c7a54603..443ac792003 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json @@ -9,7 +9,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -83,7 +83,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -98,7 +98,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -155,7 +155,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -170,7 +170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -231,7 +231,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -246,7 +246,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -306,7 +306,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -321,7 +321,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -378,7 +378,7 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -393,10 +393,10 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0/fake_runner_mix_fail_itr_test_level.py\", line 32, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir/fake_runner_mix_fail_itr_test_level.py\", line 32, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -455,7 +455,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -471,7 +471,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -525,7 +525,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -541,7 +541,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -595,7 +595,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -610,7 +610,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -663,7 +663,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -679,7 +679,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -733,7 +733,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -748,7 +748,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -801,7 +801,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", @@ -817,7 +817,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -871,7 +871,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -886,7 +886,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -939,7 +939,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -954,7 +954,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1007,7 +1007,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", @@ -1023,7 +1023,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1077,7 +1077,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1092,7 +1092,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1146,7 +1146,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1161,7 +1161,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1215,7 +1215,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1230,7 +1230,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1289,7 +1289,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1304,7 +1304,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1367,7 +1367,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1382,7 +1382,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1442,7 +1442,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1457,7 +1457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1518,7 +1518,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1533,7 +1533,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1593,7 +1593,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1608,7 +1608,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1668,7 +1668,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1683,7 +1683,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1746,7 +1746,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1761,7 +1761,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1820,7 +1820,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1835,7 +1835,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1894,7 +1894,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1909,7 +1909,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1968,7 +1968,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -1983,7 +1983,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2042,7 +2042,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2057,7 +2057,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2116,7 +2116,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2131,7 +2131,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2190,7 +2190,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2205,7 +2205,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2265,7 +2265,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2280,7 +2280,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2340,7 +2340,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2355,7 +2355,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2415,7 +2415,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2430,7 +2430,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2487,7 +2487,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2502,7 +2502,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2558,7 +2558,7 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", @@ -2573,7 +2573,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/test_manual_api_fake_runner_mix_fail_itr_test_level0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json index fa961018f8d..5a1385bc16d 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -61,11 +61,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -111,11 +111,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -164,11 +164,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -217,11 +217,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -267,11 +267,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -319,11 +319,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -363,11 +363,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -407,11 +407,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -451,11 +451,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -495,11 +495,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -540,11 +540,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -592,11 +592,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -648,11 +648,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -701,11 +701,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -754,11 +754,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -807,11 +807,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", @@ -860,11 +860,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/test_manual_api_fake_runner_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", "component": "dd_manual_test_fw", "language": "python", "library_version": "2.13.0.dev177+g25e628f0e.d20240910", diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_pin.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_pin.json index 664bd4da244..8c965c85342 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_pin.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_pin.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aiohttp_client", @@ -41,7 +41,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "aiohttp" }, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[None].json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[None].json index 4ebeae834a9..bfc8786e65d 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[None].json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[None].json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v0].json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v0].json index a5248a9c9b5..aa7a9cc810c 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v0].json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v0].json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v1].json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v1].json index 898fb650d10..5e86a680e6b 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v1].json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_unspecified_service_name_env[v1].json @@ -1,7 +1,7 @@ [[ { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "http.client.request", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "TCPConnector.connect", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v0].json b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v0].json index f53a172c9a1..feb56d61490 100644 --- a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v0].json +++ b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v0].json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aiomysql", diff --git a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v1].json b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v1].json index 87c3d3e3094..f5092a21933 100644 --- a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v1].json +++ b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_schematized_span_name[v1].json @@ -1,7 +1,7 @@ [[ { "name": "mysql.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "select 'dba4x4'", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_unspecified_service_v1.json b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_unspecified_service_v1.json index 3210687fe88..300e93fe335 100644 --- a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_unspecified_service_v1.json +++ b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_unspecified_service_v1.json @@ -1,7 +1,7 @@ [[ { "name": "mysql.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "select 'dba4x4'", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_env.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_env.json index b3d781bd8c8..e4d1379b76d 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_env.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_env.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "language": "python", "runtime-id": "b527c7abd2434e7a9f0484bebc97fc28" @@ -33,7 +33,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "aredis", "db.system": "redis", "out.host": "localhost", @@ -62,7 +62,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "aredis", "db.system": "redis", "out.host": "localhost", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v0.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v0.json index 8010e4a8120..5aaae824bd0 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v0.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v0.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v1.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v1.json index 5d34ec8dd3a..9689595d647 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v1.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_configure_service_name_env_v1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "unnamed-python-service", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "_dd.peer.service.source": "db.name", @@ -45,7 +45,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "unnamed-python-service", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "_dd.peer.service.source": "db.name", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v0].json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v0].json index 0bd8358212f..bd067bfb9ba 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v0].json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v0].json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v1].json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v1].json index 9d43cd551a8..9d8a56ee0a7 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v1].json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_span_name_by_schema[v1].json @@ -1,7 +1,7 @@ [[ { "name": "postgres.connect", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "postgres.connect", "trace_id": 0, "span_id": 1, @@ -36,7 +36,7 @@ [ { "name": "postgresql.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "SELECT 1", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v0.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v0.json index dd2baf45edf..285b0392bf0 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v0.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v0.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v1.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v1.json index ba60e2b39ed..4423955ff51 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v1.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_unspecified_service_name_env_v1.json @@ -1,7 +1,7 @@ [[ { "name": "postgres.connect", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "postgres.connect", "trace_id": 0, "span_id": 1, @@ -36,7 +36,7 @@ [ { "name": "postgresql.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "SELECT 1", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_child.json b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_child.json index ba83239a67f..1887282b06f 100644 --- a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_child.json +++ b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_child.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "pytest", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "cherrypy", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "pytest", "_dd.p.tid": "654a694400000000", "a": "b" }, diff --git a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_error.json b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_error.json index ca28cfc0fd5..49759fee3d3 100644 --- a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_error.json +++ b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_error.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "pytest", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "cherrypy", diff --git a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_fatal.json b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_fatal.json index 65208fb5cbc..4153c8c2b18 100644 --- a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_fatal.json +++ b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_fatal.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "pytest", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "cherrypy", diff --git a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_success.json b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_success.json index 97bd1380a4b..d1eb9fc5bee 100644 --- a/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_success.json +++ b/tests/snapshots/tests.contrib.cherrypy.test_middleware.test_success.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "pytest", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "cherrypy", diff --git a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch.json b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch.json index 82675526207..953a6929a1e 100644 --- a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch.json +++ b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch.json @@ -9,7 +9,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", @@ -44,7 +44,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", diff --git a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch7.json b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch7.json index 0c2c6b0f3b2..adb17b8e901 100644 --- a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch7.json +++ b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_elasticsearch7.json @@ -9,7 +9,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", @@ -41,7 +41,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", diff --git a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_opensearch.json b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_opensearch.json index d677582491b..4e969c4959a 100644 --- a/tests/snapshots/tests.contrib.elasticsearch.test_async.test_opensearch.json +++ b/tests/snapshots/tests.contrib.elasticsearch.test_async.test_opensearch.json @@ -9,7 +9,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", @@ -41,7 +41,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", diff --git a/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch.json b/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch.json index 18313a053f9..51ac329abce 100644 --- a/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch.json +++ b/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch.json @@ -9,7 +9,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", @@ -44,7 +44,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", diff --git a/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch7.json b/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch7.json index 34d93d2ef0d..9a1f72598bc 100644 --- a/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch7.json +++ b/tests/snapshots/tests.contrib.elasticsearch.test_elasticsearch_multi.test_elasticsearch7.json @@ -9,7 +9,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", @@ -43,7 +43,7 @@ "type": "elasticsearch", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "elasticsearch", diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_6_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_6_9.json index 616a8b4bd34..046ad859850 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_6_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_6_9.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9700000000", "component": "fastapi", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "fastapi", "http.method": "GET", "http.status_code": "200", @@ -64,7 +64,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "ddtrace_subprocess_dir" }, "duration": 18000, "start": 1709362583883749000 diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_9.json index 17d28e7800a..1e100e1683f 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples0]_9.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "http.request", "trace_id": 0, "span_id": 1, @@ -41,7 +41,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda900000000", "component": "fastapi", "http.method": "GET", @@ -68,7 +68,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda900000000", "component": "fastapi", "http.method": "GET", @@ -91,7 +91,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda900000000" }, "duration": 29000, diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_6_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_6_9.json index 0ff75b6149e..1ad8aca31f7 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_6_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_6_9.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9500000000", "component": "fastapi", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "fastapi", "http.method": "GET", "http.status_code": "200", @@ -64,7 +64,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "ddtrace_subprocess_dir" }, "duration": 19000, "start": 1709362581778610000 diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_9.json index 3b39cec495b..5dc0296d184 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples1]_9.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "http.request", "trace_id": 0, "span_id": 1, @@ -41,7 +41,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda800000000", "component": "fastapi", "http.method": "GET", @@ -68,7 +68,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda800000000", "component": "fastapi", "http.method": "GET", @@ -91,7 +91,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "65e2cda800000000" }, "duration": 28000, diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_6_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_6_9.json index b0e8d0b47df..38cb6209df3 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_6_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_6_9.json @@ -1,7 +1,7 @@ [[ { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "GET /sub-app/hello/{name}", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ }, { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "GET /hello/{name}", "trace_id": 0, "span_id": 2, @@ -54,7 +54,7 @@ }, { "name": "fastapi.serialize_response", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "fastapi.serialize_response", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_9.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_9.json index 7253645a8b4..4185b19bbb4 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_9.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_schematization[schema_tuples2]_9.json @@ -1,7 +1,7 @@ [[ { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "http.client.request", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "GET /sub-app/hello/{name}", "trace_id": 0, "span_id": 2, @@ -58,7 +58,7 @@ }, { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "GET /hello/{name}", "trace_id": 0, "span_id": 3, @@ -80,7 +80,7 @@ }, { "name": "fastapi.serialize_response", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "fastapi.serialize_response", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-None].json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-None].json index afaf0a8ede2..d440c7ce4a1 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-None].json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-None].json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v0].json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v0].json index 7aeba74cf43..5393c7b99cb 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v0].json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v0].json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v1].json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v1].json index c712f372528..ea10862bc80 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v1].json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_span_schematization[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "graphql.server.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "query HELLO { hello }", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ }, { "name": "graphql.parse", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "graphql.parse", "trace_id": 0, "span_id": 2, @@ -44,7 +44,7 @@ }, { "name": "graphql.validate", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "graphql.validate", "trace_id": 0, "span_id": 3, @@ -61,7 +61,7 @@ }, { "name": "graphql.execute", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "query HELLO { hello }", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v0.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v0.json index 5a1bc1a843b..55b0b14830d 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v0.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v0.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v1.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v1.json index 46dd53d6f2a..fcedeb77333 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v1.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_operation_name_env_v1.json @@ -1,7 +1,7 @@ [[ { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "http.client.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v1.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v1.json index b6ef66c8a1b..4a6b4c86316 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v1.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v1.json @@ -1,7 +1,7 @@ [[ { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "http.client.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json index 088cfb7b727..77a931190bc 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[None-None].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json index a0ad2cf0b8b..0daee22a4de 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v0-None].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json index 526df58767d..86303ea2c2b 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_schematized_span_service_and_operation[v1-None].json @@ -1,7 +1,7 @@ [[ { "name": "kafka.process", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "kafka.process", "trace_id": 0, "span_id": 1, @@ -39,7 +39,7 @@ [ { "name": "kafka.send", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "kafka.send", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json index f9d9ef25381..a32b08b0072 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override_env_var.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_integration.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_integration.json index 3c3b827c8bd..d0bf23b3d9e 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_integration.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_integration.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, @@ -46,7 +46,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, @@ -96,7 +96,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-None].json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-None].json index 773d07ae486..5698f8f83b1 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-None].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-None].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, @@ -46,7 +46,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, @@ -96,7 +96,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v0].json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v0].json index 6e5239fb0e1..cb87e41b4d0 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v0].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v0].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, @@ -46,7 +46,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, @@ -96,7 +96,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v1].json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v1].json index 22b581a8b51..d3a0104bb8d 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v1].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_service_name[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, @@ -46,7 +46,7 @@ }, { "name": "openai.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, @@ -88,7 +88,7 @@ }, { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "POST /v1/completions", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_integration.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_integration.json index 8e24a985db4..216e12124d0 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_integration.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_integration.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, @@ -45,7 +45,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-None].json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-None].json index 7d76d428d34..5f5b96687de 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-None].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-None].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, @@ -45,7 +45,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v0].json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v0].json index 8ed5af284e0..fa19c5f7a95 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v0].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v0].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, @@ -45,7 +45,7 @@ }, { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v1].json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v1].json index e14573c76af..d8d70bc7b4a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v1].json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_service_name[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, @@ -45,7 +45,7 @@ }, { "name": "openai.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_post_1_1.json index 2748d9df950..e78b5b4463d 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_pre_1_1.json index 25d78110b4d..c649cd3c11f 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema0]_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_post_1_1.json index 69ce90c03cc..7378ebb4387 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_pre_1_1.json index 76663ba9648..b14b73bb3ef 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema1]_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_post_1_1.json index 73bfbdbcf9a..5b9d07a5d72 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_post_1_1.json @@ -1,7 +1,7 @@ [[ { "name": "mariadb.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "SELECT 1", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_pre_1_1.json index f9e3f0f4922..c21bb4b23a6 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_schematized_service_and_operation[service_schema2]_pre_1_1.json @@ -1,7 +1,7 @@ [[ { "name": "mariadb.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "SELECT 1", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_post_1_1.json index 5c5708fd897..f2093493aa2 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_pre_1_1.json index 095e218a306..38324975a15 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_user_specified_dd_mariadb_service_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_integration_async.json b/tests/snapshots/tests.contrib.openai.test_openai.test_integration_async.json index 59a3807dc96..f3d41cb4b9c 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_integration_async.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_integration_async.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, @@ -18,14 +18,18 @@ "openai.organization.name": "datadog-4", "openai.request.client": "OpenAI", "openai.request.endpoint": "/v1/completions", + "openai.request.max_tokens": "150", "openai.request.method": "POST", - "openai.request.model": "ada", - "openai.request.prompt.0": "hello world", + "openai.request.model": "curie", + "openai.request.n": "1", + "openai.request.prompt.0": "As Descartes said, I think, therefore", + "openai.request.temperature": "0.8", + "openai.request.user": "ddtrace-test", "openai.response.choices.0.finish_reason": "length", - "openai.response.choices.0.text": ",\" a male farrago, or \"voices of the people.\" If", - "openai.response.created": "1681852801", - "openai.response.id": "cmpl-76n21v67JBLBTiNAIzghImtsNjBrq", - "openai.response.model": "ada", + "openai.response.choices.0.text": " I am; and I am in a sense a non-human entity woven together from memories, desires and emotions. But, who is to say that I am n...", + "openai.response.created": "1681852797", + "openai.response.id": "cmpl-76n1xq0xOyZWMQ8h6FQ89lgWjPz12", + "openai.response.model": "curie", "openai.user.api_key": "sk-...key>", "runtime-id": "78b7c1e9b7834d2ea87da266906e51a2" }, @@ -37,7 +41,7 @@ "openai.organization.ratelimit.requests.limit": 3000, "openai.organization.ratelimit.requests.remaining": 2999, "openai.organization.ratelimit.tokens.limit": 250000, - "openai.organization.ratelimit.tokens.remaining": 249983, + "openai.organization.ratelimit.tokens.remaining": 249850, "process_id": 7671 }, "duration": 1772243, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_integration_sync.json b/tests/snapshots/tests.contrib.openai.test_openai.test_integration_sync.json index 773458d0c0b..626abda72c8 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_integration_sync.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_integration_sync.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, @@ -59,7 +59,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-None].json b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-None].json index 70f8874d1f4..65eec00d960 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-None].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-None].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, @@ -59,7 +59,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v0].json b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v0].json index ba38f550527..d6417fb5667 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v0].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v0].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, @@ -59,7 +59,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "requests", "http.method": "POST", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v1].json b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v1].json index 3e4f44ca734..979ea768ef5 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v1].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v0.test_integration_service_name[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, @@ -51,7 +51,7 @@ }, { "name": "http.client.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "POST /v1/completions", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_async.json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_async.json new file mode 100644 index 00000000000..9cfd3a107cd --- /dev/null +++ b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_async.json @@ -0,0 +1,51 @@ +[[ + { + "name": "openai.request", + "service": "ddtrace_subprocess_dir", + "resource": "createCompletion", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6567512a00000000", + "component": "openai", + "language": "python", + "openai.base_url": "https://api.openai.com/v1/", + "openai.organization.name": "datadog-4", + "openai.request.client": "OpenAI", + "openai.request.endpoint": "/v1/completions", + "openai.request.max_tokens": "10", + "openai.request.method": "POST", + "openai.request.model": "ada", + "openai.request.n": "2", + "openai.request.prompt.0": "Hello world", + "openai.request.stop": ".", + "openai.request.temperature": "0.8", + "openai.request.user": "ddtrace-test", + "openai.response.choices.0.finish_reason": "length", + "openai.response.choices.0.text": ", relax!\u201d I said to my laptop", + "openai.response.choices.1.finish_reason": "stop", + "openai.response.choices.1.text": " (1", + "openai.response.created": "1681852797", + "openai.response.id": "cmpl-76n1xLvRKv3mfjx7hJ41UHrHy9ar6", + "openai.response.model": "ada", + "openai.user.api_key": "sk-...key>", + "runtime-id": "0df8b1821db8449bb0331476cea74166" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "openai.organization.ratelimit.requests.limit": 3000, + "openai.organization.ratelimit.requests.remaining": 2999, + "openai.organization.ratelimit.tokens.limit": 250000, + "openai.organization.ratelimit.tokens.remaining": 249979, + "process_id": 24448 + }, + "duration": 17466000, + "start": 1701269802293573000 + }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-None].json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-None].json index 17a28935f3b..3361ea38b5c 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-None].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-None].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v0].json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v0].json index 49e0fa4a4a9..9815b378221 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v0].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v0].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v1].json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v1].json index 2a258b9da88..3c9e6612d78 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v1].json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_service_name[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_sync.json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_sync.json index 4f73445d226..9cfd3a107cd 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_sync.json +++ b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_integration_sync.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced.json b/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced.json index 32172a0f028..3ab4a071805 100644 --- a/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced.json +++ b/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "psycopg", diff --git a/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced_via_env.json b/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced_via_env.json new file mode 100644 index 00000000000..05de2315914 --- /dev/null +++ b/tests/snapshots/tests.contrib.psycopg.test_psycopg_snapshot.test_connect_traced_via_env.json @@ -0,0 +1,30 @@ +[[ + { + "name": "psycopg.connection.connect", + "service": "postgres", + "resource": "psycopg.connection.connect", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "672bb03d00000000", + "component": "psycopg", + "db.system": "postgresql", + "language": "python", + "runtime-id": "cb42548710de494d94d2000afc8b62e8", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 99101 + }, + "duration": 12353000, + "start": 1730916413457503000 + }]] diff --git a/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced.json b/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced.json index d362b27e256..0c1664570db 100644 --- a/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced.json +++ b/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "psycopg", diff --git a/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced_via_env.json b/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced_via_env.json new file mode 100644 index 00000000000..4a064bf2b5c --- /dev/null +++ b/tests/snapshots/tests.contrib.psycopg2.test_psycopg_snapshot.test_connect_traced_via_env.json @@ -0,0 +1,30 @@ +[[ + { + "name": "psycopg2.connect", + "service": "postgres", + "resource": "psycopg2.connect", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "672bb07300000000", + "component": "psycopg", + "db.system": "postgresql", + "language": "python", + "runtime-id": "8166f0c068d04b2f9b5ee065e819d7c0", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 689 + }, + "duration": 11286000, + "start": 1730916467898579000 + }]] diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_will_include_lines_pct.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_will_include_lines_pct.json index 8da8698778c..5f08f928066 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_will_include_lines_pct.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_will_include_lines_pct.json @@ -9,7 +9,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197dd00000000", @@ -53,7 +52,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197dd00000000", @@ -96,7 +94,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197dd00000000", @@ -140,7 +137,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197dd00000000", diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_wont_include_lines_pct_if_report_empty.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_wont_include_lines_pct_if_report_empty.json index 0f1785bd408..e74ec35e8f0 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_wont_include_lines_pct_if_report_empty.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_wont_include_lines_pct_if_report_empty.json @@ -9,7 +9,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197d900000000", @@ -52,7 +51,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197d900000000", @@ -95,7 +93,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197d900000000", @@ -139,7 +136,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e197d900000000", diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json index 54924d47512..da0c84ec304 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json @@ -9,7 +9,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1983700000000", @@ -60,7 +59,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1983700000000", @@ -103,7 +101,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1983700000000", @@ -146,7 +143,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1983700000000", diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json index e37516fc9d5..ff002effa36 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json @@ -9,7 +9,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1984b00000000", @@ -60,7 +59,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1984b00000000", @@ -103,7 +101,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1984b00000000", @@ -146,7 +143,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66e1984b00000000", diff --git a/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_env.json b/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_env.json index 7e32f10df37..b9ae903a354 100644 --- a/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_env.json +++ b/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_env.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "language": "python", "runtime-id": "f771a3107ab24471ad9b83b6ca492fba" @@ -33,7 +33,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "rediscluster", "db.system": "redis", "redis.raw_command": "GET put_key_in_resource", @@ -58,7 +58,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "rediscluster", "db.system": "redis", "redis.raw_command": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0].json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0].json index cbea3972649..2e1c383a465 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0].json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "__call__() got an unexpected keyword argument 'key'", @@ -93,7 +93,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0]_pre_1_10_1.json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0]_pre_1_10_1.json index d6df44b0378..40064ec114a 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0]_pre_1_10_1.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema0]_pre_1_10_1.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "__call__() got an unexpected keyword argument 'key'", @@ -93,7 +93,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1].json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1].json index 9c563bc1395..0e4a54f65f9 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1].json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "__call__() got an unexpected keyword argument 'key'", @@ -93,7 +93,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1]_pre_1_10_1.json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1]_pre_1_10_1.json index 0e8b147aae9..531a8125c78 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1]_pre_1_10_1.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema1]_pre_1_10_1.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "__call__() got an unexpected keyword argument 'key'", @@ -93,7 +93,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2].json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2].json index 811a368f412..6cc82b6b391 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2].json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2].json @@ -1,7 +1,7 @@ [[ { "name": "rq.send", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "tests.contrib.rq.jobs.JobClass.__call__", "trace_id": 0, "span_id": 1, @@ -30,7 +30,7 @@ }, { "name": "rq.worker.perform_job", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "__call__", "trace_id": 0, "span_id": 2, @@ -61,7 +61,7 @@ }, { "name": "rq.job.perform", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "__call__", "trace_id": 0, "span_id": 3, @@ -82,7 +82,7 @@ [ { "name": "rq.process", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "rq.process", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2]_pre_1_10_1.json b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2]_pre_1_10_1.json index 598e505dc3d..9a80d9b13b9 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2]_pre_1_10_1.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_schematization[service_schema2]_pre_1_10_1.json @@ -1,7 +1,7 @@ [[ { "name": "rq.send", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "tests.contrib.rq.jobs.JobClass.__call__", "trace_id": 0, "span_id": 1, @@ -30,7 +30,7 @@ }, { "name": "rq.worker.perform_job", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "__call__", "trace_id": 0, "span_id": 2, @@ -61,7 +61,7 @@ }, { "name": "rq.job.perform", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "__call__", "trace_id": 0, "span_id": 3, @@ -82,7 +82,7 @@ [ { "name": "rq.process", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "rq.process", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_multiple_requests_sanic_http_pre_21.9.json b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_multiple_requests_sanic_http_pre_21.9.json index 8ee6f24dd9d..4aac2721c02 100644 --- a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_multiple_requests_sanic_http_pre_21.9.json +++ b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_multiple_requests_sanic_http_pre_21.9.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000" }, "duration": 39386375, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", @@ -88,7 +88,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000" }, "duration": 38914250, diff --git a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors.json b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors.json index 81ac121580c..435bbbc7b23 100644 --- a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors.json +++ b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", @@ -41,7 +41,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", diff --git a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors_pre_21.9.json b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors_pre_21.9.json index d39de38fd25..c03813a43d6 100644 --- a/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors_pre_21.9.json +++ b/tests/snapshots/tests.contrib.sanic.test_sanic_server.test_sanic_errors_pre_21.9.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", @@ -41,7 +41,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "sanic", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema0].json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema0].json index a51e143e81d..9a2aed95ceb 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema0].json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema0].json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767400000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema1].json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema1].json index e8b36e62396..0db3e31524d 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema1].json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema1].json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "66b2766e00000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema2].json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema2].json index 682ab903661..0e0b81ea5ae 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema2].json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_schematization[service_schema2].json @@ -1,7 +1,7 @@ [[ { "name": "sql.query", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "select current_version();", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_generates_source_file_data.json b/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_generates_source_file_data.json index bef153f39d8..9c2a4909c10 100644 --- a/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_generates_source_file_data.json +++ b/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_generates_source_file_data.json @@ -245,7 +245,7 @@ "_dd.p.tid": "66f2725d00000000", "component": "unittest", "error.message": "", - "error.stack": "Traceback (most recent call last):\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n yield\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 592, in run\n self._callTestMethod(testMethod)\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n method()\n File \"/tmp/pytest-of-root/pytest-328/test_unittest_generates_source_file_data0/test_source_file.py\", line 12, in test_third\n assert ret_false()\nAssertionError\n", + "error.stack": "Traceback (most recent call last):\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n yield\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 592, in run\n self._callTestMethod(testMethod)\n File \"/root/.pyenv/versions/3.9.19/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n method()\n File \"/ddtrace_subprocess_dir/pytest-of-root/pytest-328/test_unittest_generates_source_file_data0/test_source_file.py\", line 12, in test_third\n assert ret_false()\nAssertionError\n", "error.type": "builtins.AssertionError", "language": "python", "library_version": "2.14.0.dev72+g6df23bd02", diff --git a/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_will_include_lines_pct.json b/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_will_include_lines_pct.json index 8dec69b3952..bf4b50bd98c 100644 --- a/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_will_include_lines_pct.json +++ b/tests/snapshots/tests.contrib.unittest.test_unittest_snapshot.test_unittest_will_include_lines_pct.json @@ -9,7 +9,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66f2723900000000", @@ -54,7 +53,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66f2723900000000", @@ -98,7 +96,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66f2723900000000", @@ -142,7 +139,6 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", "_dd.p.tid": "66f2723900000000", diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-None].json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-None].json index 90807b739b9..6944e882d5e 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-None].json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-None].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v0].json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v0].json index 79fd70c1a1a..18b1551318f 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v0].json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v0].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v1].json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v1].json index dc6602016d6..697dcf2febc 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v1].json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_schematization[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "GET /", "trace_id": 0, "span_id": 1, @@ -9,6 +9,7 @@ "type": "web", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -31,7 +32,7 @@ }, { "name": "wsgi.application", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.application", "trace_id": 0, "span_id": 2, @@ -39,6 +40,7 @@ "type": "", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -47,7 +49,7 @@ }, { "name": "wsgi.start_response", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.start_response", "trace_id": 0, "span_id": 4, @@ -55,6 +57,7 @@ "type": "web", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -64,7 +67,7 @@ }, { "name": "wsgi.response", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.response", "trace_id": 0, "span_id": 3, @@ -72,6 +75,7 @@ "type": "", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list" diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_env.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_env.json index d57013d6602..09c08bf56a0 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_env.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_env.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "language": "python", "runtime-id": "3c2060b13ff1469387b2c823d7d43f18" @@ -33,7 +33,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "yaaredis", "db.system": "redis", "out.host": "localhost", @@ -62,7 +62,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "component": "yaaredis", "db.system": "redis", "out.host": "localhost", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema0].json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema0].json index 17db48d12f3..d541510cbcd 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema0].json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema0].json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema1].json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema1].json index e9099a70c6b..f0075f33b51 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema1].json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema1].json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema2].json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema2].json index 30a9013e9d8..f1067bca0f9 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema2].json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_schematization[service_schema2].json @@ -1,7 +1,7 @@ [[ { "name": "redis.command", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "FLUSHALL", "trace_id": 0, "span_id": 1, @@ -38,7 +38,7 @@ [ { "name": "redis.command", - "service": "unnamed-python-service", + "service": "ddtrace_subprocess_dir", "resource": "GET", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_context_snapshots.test_context_multiprocess.json b/tests/snapshots/tests.integration.test_context_snapshots.test_context_multiprocess.json index d49e67016a1..37c2d193cf3 100644 --- a/tests/snapshots/tests.integration.test_context_snapshots.test_context_multiprocess.json +++ b/tests/snapshots/tests.integration.test_context_snapshots.test_context_multiprocess.json @@ -1,7 +1,7 @@ [[ { "name": "work", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "work", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "proc", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "proc", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_async.json b/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_async.json index 1d4181dbadb..0a6586d835a 100644 --- a/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_async.json +++ b/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_async.json @@ -1,7 +1,7 @@ [[ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_async_test_method", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_async_test_method2", "trace_id": 1, "span_id": 1, @@ -51,7 +51,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_Class.async_test_method", "trace_id": 2, "span_id": 1, @@ -76,7 +76,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_Class.async_test_method", "trace_id": 3, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_sync.json b/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_sync.json index 7d042a0d318..af510abaf34 100644 --- a/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_sync.json +++ b/tests/snapshots/tests.integration.test_tracemethods.test_ddtrace_run_trace_methods_sync.json @@ -1,7 +1,7 @@ [[ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_test_method", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_test_method2", "trace_id": 1, "span_id": 1, @@ -50,7 +50,7 @@ }, { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_test_method", "trace_id": 1, "span_id": 2, @@ -66,7 +66,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_Class.test_method", "trace_id": 2, "span_id": 1, @@ -91,7 +91,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_Class.test_method2", "trace_id": 3, "span_id": 1, @@ -115,7 +115,7 @@ }, { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "_Class.test_method", "trace_id": 3, "span_id": 2, @@ -131,7 +131,7 @@ [ { "name": "trace.annotation", - "service": "", + "service": "ddtrace_subprocess_dir", "resource": "NestedClass.test_method", "trace_id": 4, "span_id": 1, diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 4adc232cc6e..df107e03172 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -17,6 +17,7 @@ from ddtrace.internal.utils.version import _pep440_to_semver from ddtrace.settings import _config as config from ddtrace.settings.config import DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT +from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import override_global_config @@ -404,7 +405,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_REMOTE_CONFIGURATION_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", "origin": "env_var", "value": 1.0}, {"name": "DD_RUNTIME_METRICS_ENABLED", "origin": "unknown", "value": False}, - {"name": "DD_SERVICE", "origin": "default", "value": "unnamed-python-service"}, + {"name": "DD_SERVICE", "origin": "default", "value": DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME}, {"name": "DD_SERVICE_MAPPING", "origin": "env_var", "value": "default_dd_service:remapped_dd_service"}, {"name": "DD_SITE", "origin": "env_var", "value": "datadoghq.com"}, {"name": "DD_SPAN_SAMPLING_RULES", "origin": "env_var", "value": '[{"service":"xyz", "sample_rate":0.23}]'}, diff --git a/tests/tracer/runtime/test_runtime_metrics.py b/tests/tracer/runtime/test_runtime_metrics.py index 7c44fb25135..399b536625a 100644 --- a/tests/tracer/runtime/test_runtime_metrics.py +++ b/tests/tracer/runtime/test_runtime_metrics.py @@ -76,10 +76,10 @@ def test_runtime_tags_empty(): from ddtrace.internal.runtime.runtime_metrics import RuntimeTags tags = list(RuntimeTags()) - assert len(tags) == 4 + assert len(tags) == 5 tags = dict(tags) - assert set(tags.keys()) == set(["lang", "lang_interpreter", "lang_version", "tracer_version"]) + assert set(tags.keys()) == set(["lang", "lang_interpreter", "lang_version", "tracer_version", "service"]) @pytest.mark.subprocess(env={"DD_SERVICE": "my-service", "DD_ENV": "test-env", "DD_VERSION": "1.2.3"}) @@ -103,11 +103,11 @@ def test_runtime_tags_dd_tags(): from ddtrace.internal.runtime.runtime_metrics import RuntimeTags tags = list(RuntimeTags()) - assert len(tags) == 7, tags + assert len(tags) == 8, tags tags = dict(tags) assert set(tags.keys()) == set( - ["lang", "lang_interpreter", "lang_version", "tracer_version", "version", "custom", "test"] + ["lang", "lang_interpreter", "lang_version", "tracer_version", "version", "custom", "test", "service"] ) assert tags["custom"] == "tag" assert tags["test"] == "key" @@ -122,10 +122,10 @@ def test_runtime_tags_manual_tracer_tags(): tracer.set_tags({"manual": "tag"}) tags = list(RuntimeTags()) - assert len(tags) == 5, tags + assert len(tags) == 6, tags tags = dict(tags) - assert set(tags.keys()) == set(["lang", "lang_interpreter", "lang_version", "tracer_version", "manual"]) + assert set(tags.keys()) == set(["lang", "lang_interpreter", "lang_version", "tracer_version", "manual", "service"]) assert tags["manual"] == "tag" diff --git a/tests/tracer/runtime/test_tag_collectors.py b/tests/tracer/runtime/test_tag_collectors.py index 07b7f63c144..3889b7b7e15 100644 --- a/tests/tracer/runtime/test_tag_collectors.py +++ b/tests/tracer/runtime/test_tag_collectors.py @@ -45,6 +45,7 @@ def test_tracer_tags_config(): """Ensure we collect the expected tags for the TracerTagCollector""" import ddtrace from ddtrace.internal.runtime import tag_collectors + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME # DEV: setting `config.tags` does not work, they get copied to `tracer._tags` # when the tracer is created @@ -64,9 +65,10 @@ def test_tracer_tags_config(): assert values is not None assert set(values) == set( [ + ("service", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME), ("env", "my-env"), - ("global", "global-tag"), ("version", "1.5.4"), + ("global", "global-tag"), ] ) @@ -77,6 +79,7 @@ def test_tracer_tags_service_from_code(): import ddtrace from ddtrace.filters import TraceFilter from ddtrace.internal.runtime import tag_collectors + from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME class DropFilter(TraceFilter): def process_trace(self, _): @@ -99,4 +102,10 @@ def process_trace(self, _): values = ttc.collect() assert values is not None - assert set(values) == set([("service", "my-service"), ("service", "new-service")]) + assert set(values) == set( + [ + ("service", "new-service"), + ("service", DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME), + ("service", "my-service"), + ] + ) From 71a7a6c6ead35d4c010291e761e9bfbf83e5ed49 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 7 Nov 2024 20:32:43 -0500 Subject: [PATCH 124/372] fix(lib-injection): do not import user installed ddtrace (#11317) --- lib-injection/sources/sitecustomize.py | 14 +++++++++----- .../fix-lib-injection-import-f0e5e2715854d3e0.yaml | 4 ++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/fix-lib-injection-import-f0e5e2715854d3e0.yaml diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 2d6faa78cc0..6c9b38e6dca 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -5,6 +5,7 @@ from collections import namedtuple import csv +import importlib.util import json import os import platform @@ -205,9 +206,13 @@ def _inject(): integration_incomp = False runtime_incomp = False os.environ["_DD_INJECT_WAS_ATTEMPTED"] = "true" + spec = None try: - import ddtrace - except ImportError: + # None is a valid return value for find_spec (module was not found), so we need to check for it explicitly + spec = importlib.util.find_spec("ddtrace") + if not spec: + raise ModuleNotFoundError("ddtrace") + except Exception: _log("user-installed ddtrace not found, configuring application to use injection site-packages") current_platform = "manylinux2014" if _get_clib() == "gnu" else "musllinux_1_1" @@ -347,9 +352,8 @@ def _inject(): _log("failed to load ddtrace.bootstrap.sitecustomize: %s" % e, level="error") return else: - _log( - "user-installed ddtrace found: %s, aborting site-packages injection" % ddtrace.__version__, level="warning" - ) + module_origin = spec.origin if spec else None + _log("user-installed ddtrace found: %s, aborting site-packages injection" % module_origin, level="warning") try: diff --git a/releasenotes/notes/fix-lib-injection-import-f0e5e2715854d3e0.yaml b/releasenotes/notes/fix-lib-injection-import-f0e5e2715854d3e0.yaml new file mode 100644 index 00000000000..2e0071c9a98 --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-import-f0e5e2715854d3e0.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: This fix ensures we do not import the user installed ``ddtrace`` if it is present. From 6008aa35600c210b923e9ca9412908938e43be71 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 7 Nov 2024 20:34:02 -0500 Subject: [PATCH 125/372] fix(lib-injection): increase coverage of Python lib-injection denylist (#11296) --- lib-injection/sources/denied_executables.txt | 584 ++++++++++++++++++ ...b-injection-denylist-d5478ecfef90fbcd.yaml | 4 + 2 files changed, 588 insertions(+) create mode 100644 releasenotes/notes/fix-lib-injection-denylist-d5478ecfef90fbcd.yaml diff --git a/lib-injection/sources/denied_executables.txt b/lib-injection/sources/denied_executables.txt index 39b8c2e4267..40eedfba9b2 100644 --- a/lib-injection/sources/denied_executables.txt +++ b/lib-injection/sources/denied_executables.txt @@ -617,3 +617,587 @@ usr/libexec/grepconf.sh /usr/sbin/xfsslower-bpfcc /usr/sbin/zfsdist-bpfcc /usr/sbin/zfsslower-bpfcc +# Amazon Linux 2 Python shebang +/usr/bin/aws +/usr/bin/aws_completer +/usr/bin/bno_plot.py +/usr/bin/chardetect +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/debuginfo-install +/usr/bin/easy_install +/usr/bin/easy_install-2.7 +/usr/bin/easy_install-3.7 +/usr/bin/find-repos-of-install +/usr/bin/hibagent +/usr/bin/hibinit-agent +/usr/bin/jp.py-2.7 +/usr/bin/jsonpointer +/usr/bin/jsonschema +/usr/bin/lsmcli +/usr/bin/msghack +/usr/bin/needs-restarting +/usr/bin/package-cleanup +/usr/bin/pip3 +/usr/bin/pip3.7 +/usr/bin/pydoc +/usr/bin/pydoc3.7 +/usr/bin/pyrsa-decrypt-2 +/usr/bin/pyrsa-decrypt-bigfile-2 +/usr/bin/pyrsa-encrypt-2 +/usr/bin/pyrsa-encrypt-bigfile-2 +/usr/bin/pyrsa-keygen-2 +/usr/bin/pyrsa-priv2pub-2 +/usr/bin/pyrsa-sign-2 +/usr/bin/pyrsa-verify-2 +/usr/bin/pystache +/usr/bin/pystache-3 +/usr/bin/pystache-test +/usr/bin/pystache-test-3 +/usr/bin/pyvenv-3.7 +/usr/bin/repoclosure +/usr/bin/repodiff +/usr/bin/repomanage +/usr/bin/repoquery +/usr/bin/reposync +/usr/bin/repotrack +/usr/bin/repo-graph +/usr/bin/repo-rss +/usr/bin/rst2html +/usr/bin/rst2html4-3.7 +/usr/bin/rst2html5-3.7 +/usr/bin/rst2html-3.7 +/usr/bin/rst2latex +/usr/bin/rst2latex-3.7 +/usr/bin/rst2man +/usr/bin/rst2man-3.7 +/usr/bin/rst2odt +/usr/bin/rst2odt-3.7 +/usr/bin/rst2odt_prepstyles +/usr/bin/rst2odt_prepstyles-3.7 +/usr/bin/rst2pseudoxml +/usr/bin/rst2pseudoxml-3.7 +/usr/bin/rst2s5 +/usr/bin/rst2s5-3.7 +/usr/bin/rst2xetex +/usr/bin/rst2xetex-3.7 +/usr/bin/rst2xml +/usr/bin/rst2xml-3.7 +/usr/bin/rstpep2html +/usr/bin/rstpep2html-3.7 +/usr/bin/show-changed-rco +/usr/bin/show-installed +/usr/bin/sim_lsmplugin +/usr/bin/systemd-sysv-convert +/usr/bin/urlgrabber +/usr/bin/verifytree +/usr/bin/yum +/usr/bin/yumdownloader +/usr/bin/yum-builddep +/usr/bin/yum-config-manager +/usr/bin/yum-debug-dump +/usr/bin/yum-debug-restore +/usr/bin/yum-groups-manager +/usr/sbin/ebsnvme-id +/usr/sbin/mountstats +/usr/sbin/nfsiostat +/usr/sbin/yumdb +/usr/sbin/yum-complete-transaction +# Amazon Linux 2023 Python shebang +/usr/bin/audit2allow +/usr/bin/aws +/usr/bin/aws_completer +/usr/bin/chardetect +/usr/bin/chcat +/usr/bin/chevron +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/distro +/usr/bin/dnf-3 +/usr/bin/hibinit-agent +/usr/bin/jp.py +/usr/bin/jsondiff-3.9 +/usr/bin/jsonpatch-3.9 +/usr/bin/jsonpointer-3.9 +/usr/bin/jsonschema +/usr/bin/lsmcli +/usr/bin/miniterm-3.py +/usr/bin/pydoc3.9 +/usr/bin/rst2html +/usr/bin/rst2html4 +/usr/bin/rst2html5 +/usr/bin/rst2latex +/usr/bin/rst2man +/usr/bin/rst2odt +/usr/bin/rst2odt_prepstyles +/usr/bin/rst2pseudoxml +/usr/bin/rst2s5 +/usr/bin/rst2xetex +/usr/bin/rst2xml +/usr/bin/rstpep2html +/usr/bin/sim_lsmplugin +/usr/sbin/ebsnvme-id +/usr/sbin/mountstats +/usr/sbin/nfsconvert +/usr/sbin/nfsdclddb +/usr/sbin/nfsdclnts +/usr/sbin/nfsiostat +/usr/sbin/semanage +# Amazon Linux 2018 Python shebang +/sbin/ebsnvme-id +/sbin/grub-crypt +/sbin/mount.efs +/usr/bin/amazon-efs-mount-watchdog +/usr/bin/cloud-init +/usr/bin/easy_install-2.7 +/usr/bin/easy_install-3.6 +/usr/bin/get_reference_source +/usr/bin/pip-3.6 +/usr/bin/pybabel-2.7 +/usr/bin/pydoc2.7 +/usr/bin/pydoc3.6 +/usr/bin/pyvenv3.6 +/usr/bin/urlgrabber-2.7 +/usr/bin/yum +/usr/sbin/mountstats +/usr/sbin/nfsiostat +# Ubuntu 20.04 Python shebang +/usr/bin/add-apt-repository +/usr/bin/apport-cli +/usr/bin/apport-unpack +/usr/bin/automat-visualize3 +/usr/bin/cftp3 +/usr/bin/chardetect3 +/usr/bin/check-language-support +/usr/bin/ckeygen3 +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/conch3 +/usr/bin/do-release-upgrade +/usr/bin/ec2metadata +/usr/bin/hibagent +/usr/bin/hibinit-agent +/usr/bin/hwe-support-status +/usr/bin/jsonpatch +/usr/bin/jsonpatch-jsondiff +/usr/bin/jsonpointer +/usr/bin/jsonschema +/usr/bin/keyring +/usr/bin/landscape-sysinfo +/usr/bin/lsb_release +/usr/bin/mailmail3 +/usr/bin/miniterm +/usr/bin/networkd-dispatcher +/usr/bin/oem-getlogs +/usr/bin/pastebinit +/usr/bin/py3clean +/usr/bin/py3compile +/usr/bin/pydoc3.8 +/usr/bin/pygettext3.8 +/usr/bin/pyhtmlizer3 +/usr/bin/pyjwt3 +/usr/bin/sos +/usr/bin/sosreport +/usr/bin/sos-collector +/usr/bin/ssh-import-id +/usr/bin/tkconch3 +/usr/bin/trial3 +/usr/bin/twist3 +/usr/bin/twistd3 +/usr/bin/ubuntu-advantage +/usr/bin/ubuntu-security-status +/usr/bin/unattended-upgrade +/usr/sbin/aa-status +/usr/sbin/ufw +/usr/sbin/xfs_scrub_all +# Ubuntu 22.04 Python shebang +/usr/bin/add-apt-repository +/usr/bin/apport-cli +/usr/bin/apport-unpack +/usr/bin/automat-visualize3 +/usr/bin/cftp3 +/usr/bin/chardetect +/usr/bin/ckeygen3 +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/conch3 +/usr/bin/do-release-upgrade +/usr/bin/ec2metadata +/usr/bin/hibagent +/usr/bin/hibinit-agent +/usr/bin/hwe-support-status +/usr/bin/jsonpatch +/usr/bin/jsonpointer +/usr/bin/jsonschema +/usr/bin/json-patch-jsondiff +/usr/bin/keyring +/usr/bin/landscape-sysinfo +/usr/bin/lsb_release +/usr/bin/mailmail3 +/usr/bin/networkd-dispatcher +/usr/bin/oem-getlogs +/usr/bin/pastebinit +/usr/bin/py3clean +/usr/bin/py3compile +/usr/bin/pydoc3.10 +/usr/bin/pygettext3.10 +/usr/bin/pyhtmlizer3 +/usr/bin/pyserial-miniterm +/usr/bin/pyserial-ports +/usr/bin/rrsync +/usr/bin/sos +/usr/bin/sosreport +/usr/bin/sos-collector +/usr/bin/ssh-import-id +/usr/bin/tkconch3 +/usr/bin/trial3 +/usr/bin/twist3 +/usr/bin/twistd3 +/usr/bin/ubuntu-advantage +/usr/bin/ubuntu-security-status +/usr/bin/unattended-upgrade +/usr/sbin/ufw +/usr/sbin/xfs_scrub_all +/usr/bin/add-apt-repository +/usr/bin/apport-cli +/usr/bin/apport-unpack +/usr/bin/automat-visualize3 +/usr/bin/cftp3 +/usr/bin/chardetect +/usr/bin/ckeygen3 +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/conch3 +/usr/bin/do-release-upgrade +/usr/bin/ec2metadata +/usr/bin/hibagent +/usr/bin/hibinit-agent +/usr/bin/hwe-support-status +/usr/bin/jsonpatch +/usr/bin/jsonpointer +/usr/bin/jsonschema +/usr/bin/json-patch-jsondiff +/usr/bin/landscape-sysinfo +/usr/bin/mailmail3 +/usr/bin/markdown-it +/usr/bin/netaddr +/usr/bin/networkd-dispatcher +/usr/bin/oem-getlogs +/usr/bin/pastebinit +/usr/bin/py3clean +/usr/bin/py3compile +/usr/bin/pydoc3.12 +/usr/bin/pygettext3.12 +/usr/bin/pygmentize +/usr/bin/pyhtmlizer3 +/usr/bin/pyserial-miniterm +/usr/bin/pyserial-ports +/usr/bin/routel +/usr/bin/rrsync +/usr/bin/sos +/usr/bin/sosreport +/usr/bin/sos-collector +/usr/bin/ssh-import-id +/usr/bin/tkconch3 +/usr/bin/trial3 +/usr/bin/twist3 +/usr/bin/twistd3 +/usr/bin/ubuntu-advantage +/usr/bin/ubuntu-security-status +/usr/bin/unattended-upgrade +/usr/sbin/argdist-bpfcc +/usr/sbin/bashreadline-bpfcc +/usr/sbin/bindsnoop-bpfcc +/usr/sbin/biolatency-bpfcc +/usr/sbin/biolatpcts-bpfcc +/usr/sbin/biopattern-bpfcc +/usr/sbin/biosnoop-bpfcc +/usr/sbin/biotop-bpfcc +/usr/sbin/bitesize-bpfcc +/usr/sbin/bpflist-bpfcc +/usr/sbin/btrfsdist-bpfcc +/usr/sbin/btrfsslower-bpfcc +/usr/sbin/cachestat-bpfcc +/usr/sbin/cachetop-bpfcc +/usr/sbin/capable-bpfcc +/usr/sbin/compactsnoop-bpfcc +/usr/sbin/cpudist-bpfcc +/usr/sbin/cpuunclaimed-bpfcc +/usr/sbin/criticalstat-bpfcc +/usr/sbin/dbslower-bpfcc +/usr/sbin/dbstat-bpfcc +/usr/sbin/dcsnoop-bpfcc +/usr/sbin/dcstat-bpfcc +/usr/sbin/deadlock-bpfcc +/usr/sbin/dirtop-bpfcc +/usr/sbin/drsnoop-bpfcc +/usr/sbin/execsnoop-bpfcc +/usr/sbin/exitsnoop-bpfcc +/usr/sbin/ext4dist-bpfcc +/usr/sbin/ext4slower-bpfcc +/usr/sbin/filegone-bpfcc +/usr/sbin/filelife-bpfcc +/usr/sbin/fileslower-bpfcc +/usr/sbin/filetop-bpfcc +/usr/sbin/funccount-bpfcc +/usr/sbin/funcinterval-bpfcc +/usr/sbin/funclatency-bpfcc +/usr/sbin/funcslower-bpfcc +/usr/sbin/gethostlatency-bpfcc +/usr/sbin/hardirqs-bpfcc +/usr/sbin/inject-bpfcc +/usr/sbin/killsnoop-bpfcc +/usr/sbin/klockstat-bpfcc +/usr/sbin/kvmexit-bpfcc +/usr/sbin/llcstat-bpfcc +/usr/sbin/mdflush-bpfcc +/usr/sbin/memleak-bpfcc +/usr/sbin/mountsnoop-bpfcc +/usr/sbin/mysqld_qslower-bpfcc +/usr/sbin/netqtop-bpfcc +/usr/sbin/nfsdist-bpfcc +/usr/sbin/nfsslower-bpfcc +/usr/sbin/offcputime-bpfcc +/usr/sbin/offwaketime-bpfcc +/usr/sbin/oomkill-bpfcc +/usr/sbin/opensnoop-bpfcc +/usr/sbin/pidpersec-bpfcc +/usr/sbin/ppchcalls-bpfcc +/usr/sbin/profile-bpfcc +/usr/sbin/rdmaucma-bpfcc +/usr/sbin/readahead-bpfcc +/usr/sbin/runqlat-bpfcc +/usr/sbin/runqlen-bpfcc +/usr/sbin/runqslower-bpfcc +/usr/sbin/shmsnoop-bpfcc +/usr/sbin/slabratetop-bpfcc +/usr/sbin/sofdsnoop-bpfcc +/usr/sbin/softirqs-bpfcc +/usr/sbin/solisten-bpfcc +/usr/sbin/sslsniff-bpfcc +/usr/sbin/stackcount-bpfcc +/usr/sbin/statsnoop-bpfcc +/usr/sbin/syncsnoop-bpfcc +/usr/sbin/syscount-bpfcc +/usr/sbin/tcpaccept-bpfcc +/usr/sbin/tcpcong-bpfcc +/usr/sbin/tcpconnect-bpfcc +/usr/sbin/tcpconnlat-bpfcc +/usr/sbin/tcpdrop-bpfcc +/usr/sbin/tcplife-bpfcc +/usr/sbin/tcpretrans-bpfcc +/usr/sbin/tcprtt-bpfcc +/usr/sbin/tcpstates-bpfcc +/usr/sbin/tcpsubnet-bpfcc +/usr/sbin/tcpsynbl-bpfcc +/usr/sbin/tcptop-bpfcc +/usr/sbin/tcptracer-bpfcc +/usr/sbin/threadsnoop-bpfcc +/usr/sbin/tplist-bpfcc +/usr/sbin/trace-bpfcc +/usr/sbin/ttysnoop-bpfcc +/usr/sbin/ucalls +/usr/sbin/uflow +/usr/sbin/ufw +/usr/sbin/ugc +/usr/sbin/uobjnew +/usr/sbin/ustat +/usr/sbin/uthreads +/usr/sbin/vfscount-bpfcc +/usr/sbin/vfsstat-bpfcc +/usr/sbin/virtiostat-bpfcc +/usr/sbin/wakeuptime-bpfcc +/usr/sbin/xfsdist-bpfcc +/usr/sbin/xfsslower-bpfcc +/usr/sbin/xfs_scrub_all +/usr/sbin/zfsdist-bpfcc +/usr/sbin/zfsslower-bpfcc +# Ubuntu 24.10 Python shebang +/usr/bin/add-apt-repository +/usr/bin/apport-cli +/usr/bin/apport-unpack +/usr/bin/automat-visualize3 +/usr/bin/cftp3 +/usr/bin/chardetect +/usr/bin/ckeygen3 +/usr/bin/cloud-id +/usr/bin/cloud-init +/usr/bin/conch3 +/usr/bin/do-release-upgrade +/usr/bin/ec2metadata +/usr/bin/hibagent +/usr/bin/hibinit-agent +/usr/bin/hwe-support-status +/usr/bin/jsonpatch +/usr/bin/jsonpointer +/usr/bin/jsonschema +/usr/bin/json-patch-jsondiff +/usr/bin/landscape-sysinfo +/usr/bin/mailmail3 +/usr/bin/markdown-it +/usr/bin/netaddr +/usr/bin/networkd-dispatcher +/usr/bin/oem-getlogs +/usr/bin/pastebinit +/usr/bin/py3clean +/usr/bin/py3compile +/usr/bin/pydoc3.12 +/usr/bin/pygettext3.12 +/usr/bin/pygmentize +/usr/bin/pyhtmlizer3 +/usr/bin/pyserial-miniterm +/usr/bin/pyserial-ports +/usr/bin/routel +/usr/bin/rrsync +/usr/bin/sos +/usr/bin/sosreport +/usr/bin/sos-collector +/usr/bin/ssh-import-id +/usr/bin/tkconch3 +/usr/bin/trial3 +/usr/bin/twist3 +/usr/bin/twistd3 +/usr/bin/ubuntu-advantage +/usr/bin/ubuntu-security-status +/usr/bin/unattended-upgrade +/usr/sbin/argdist-bpfcc +/usr/sbin/bashreadline-bpfcc +/usr/sbin/bindsnoop-bpfcc +/usr/sbin/biolatency-bpfcc +/usr/sbin/biolatpcts-bpfcc +/usr/sbin/biopattern-bpfcc +/usr/sbin/biosnoop-bpfcc +/usr/sbin/biotop-bpfcc +/usr/sbin/bitesize-bpfcc +/usr/sbin/bpflist-bpfcc +/usr/sbin/btrfsdist-bpfcc +/usr/sbin/btrfsslower-bpfcc +/usr/sbin/cachestat-bpfcc +/usr/sbin/cachetop-bpfcc +/usr/sbin/capable-bpfcc +/usr/sbin/compactsnoop-bpfcc +/usr/sbin/cpudist-bpfcc +/usr/sbin/cpuunclaimed-bpfcc +/usr/sbin/criticalstat-bpfcc +/usr/sbin/dbslower-bpfcc +/usr/sbin/dbstat-bpfcc +/usr/sbin/dcsnoop-bpfcc +/usr/sbin/dcstat-bpfcc +/usr/sbin/deadlock-bpfcc +/usr/sbin/dirtop-bpfcc +/usr/sbin/drsnoop-bpfcc +/usr/sbin/execsnoop-bpfcc +/usr/sbin/exitsnoop-bpfcc +/usr/sbin/ext4dist-bpfcc +/usr/sbin/ext4slower-bpfcc +/usr/sbin/f2fsslower-bpfcc +/usr/sbin/filegone-bpfcc +/usr/sbin/filelife-bpfcc +/usr/sbin/fileslower-bpfcc +/usr/sbin/filetop-bpfcc +/usr/sbin/funccount-bpfcc +/usr/sbin/funcinterval-bpfcc +/usr/sbin/funclatency-bpfcc +/usr/sbin/funcslower-bpfcc +/usr/sbin/gethostlatency-bpfcc +/usr/sbin/hardirqs-bpfcc +/usr/sbin/inject-bpfcc +/usr/sbin/killsnoop-bpfcc +/usr/sbin/klockstat-bpfcc +/usr/sbin/kvmexit-bpfcc +/usr/sbin/llcstat-bpfcc +/usr/sbin/mdflush-bpfcc +/usr/sbin/memleak-bpfcc +/usr/sbin/mountsnoop-bpfcc +/usr/sbin/mysqld_qslower-bpfcc +/usr/sbin/netqtop-bpfcc +/usr/sbin/nfsdist-bpfcc +/usr/sbin/nfsslower-bpfcc +/usr/sbin/offcputime-bpfcc +/usr/sbin/offwaketime-bpfcc +/usr/sbin/oomkill-bpfcc +/usr/sbin/opensnoop-bpfcc +/usr/sbin/pidpersec-bpfcc +/usr/sbin/ppchcalls-bpfcc +/usr/sbin/profile-bpfcc +/usr/sbin/rdmaucma-bpfcc +/usr/sbin/readahead-bpfcc +/usr/sbin/runqlat-bpfcc +/usr/sbin/runqlen-bpfcc +/usr/sbin/runqslower-bpfcc +/usr/sbin/shmsnoop-bpfcc +/usr/sbin/slabratetop-bpfcc +/usr/sbin/sofdsnoop-bpfcc +/usr/sbin/softirqs-bpfcc +/usr/sbin/solisten-bpfcc +/usr/sbin/sslsniff-bpfcc +/usr/sbin/stackcount-bpfcc +/usr/sbin/statsnoop-bpfcc +/usr/sbin/syncsnoop-bpfcc +/usr/sbin/syscount-bpfcc +/usr/sbin/tcpaccept-bpfcc +/usr/sbin/tcpcong-bpfcc +/usr/sbin/tcpconnect-bpfcc +/usr/sbin/tcpconnlat-bpfcc +/usr/sbin/tcpdrop-bpfcc +/usr/sbin/tcplife-bpfcc +/usr/sbin/tcpretrans-bpfcc +/usr/sbin/tcprtt-bpfcc +/usr/sbin/tcpstates-bpfcc +/usr/sbin/tcpsubnet-bpfcc +/usr/sbin/tcpsynbl-bpfcc +/usr/sbin/tcptop-bpfcc +/usr/sbin/tcptracer-bpfcc +/usr/sbin/threadsnoop-bpfcc +/usr/sbin/tplist-bpfcc +/usr/sbin/trace-bpfcc +/usr/sbin/ttysnoop-bpfcc +/usr/sbin/ucalls +/usr/sbin/uflow +/usr/sbin/ufw +/usr/sbin/ugc +/usr/sbin/uobjnew +/usr/sbin/ustat +/usr/sbin/uthreads +/usr/sbin/vfscount-bpfcc +/usr/sbin/vfsstat-bpfcc +/usr/sbin/virtiostat-bpfcc +/usr/sbin/wakeuptime-bpfcc +/usr/sbin/wqlat-bpfcc +/usr/sbin/xfsdist-bpfcc +/usr/sbin/xfsslower-bpfcc +/usr/sbin/xfs_scrub_all +/usr/sbin/zfsdist-bpfcc +/usr/sbin/zfsslower-bpfcc +# Debian 11 Python shebang +/usr/bin/2to3-2.7 +/usr/bin/apt-listchanges +/usr/bin/asan_symbolize-11 +/usr/bin/audit2allow +/usr/bin/chardetect +/usr/bin/chcat +/usr/bin/debianbts +/usr/bin/dtrace +/usr/bin/f2py3 +/usr/bin/f2py3.9 +/usr/bin/lsb_release +/usr/bin/policygentool +/usr/bin/py3clean +/usr/bin/py3compile +/usr/bin/pyclean +/usr/bin/pycompile +/usr/bin/pydoc2.7 +/usr/bin/pydoc3.9 +/usr/bin/pygettext2.7 +/usr/bin/pygettext3.9 +/usr/bin/pygmentize +/usr/bin/querybts +/usr/bin/reportbug +/usr/bin/sandbox +/usr/bin/sediff +/usr/bin/sedta +/usr/bin/seinfo +/usr/bin/seinfoflow +/usr/bin/sepolgen-ifgen +/usr/bin/sepolicy +/usr/bin/sesearch diff --git a/releasenotes/notes/fix-lib-injection-denylist-d5478ecfef90fbcd.yaml b/releasenotes/notes/fix-lib-injection-denylist-d5478ecfef90fbcd.yaml new file mode 100644 index 00000000000..a25f2904b16 --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-denylist-d5478ecfef90fbcd.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: This fix adds more commands to the auto-injection denylist. From b3e9bc3ff09f25d6a7e0c6c9a40ccde9461ab55d Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:35:17 -0600 Subject: [PATCH 126/372] fix: update libdatadog to v14, which fixes crashtracking (#11294) Co-authored-by: Brett Langdon --- ddtrace/internal/core/crashtracking.py | 4 +- .../profiling/cmake/FindLibdatadog.cmake | 2 +- .../cmake/tools/libdatadog_checksums.txt | 10 +- .../profiling/crashtracker/_crashtracker.pyi | 4 +- .../profiling/crashtracker/_crashtracker.pyx | 12 +- .../dd_wrapper/include/constants.hpp | 4 +- .../dd_wrapper/include/crashtracker.hpp | 9 +- .../include/crashtracker_interface.hpp | 4 +- .../profiling/dd_wrapper/src/crashtracker.cpp | 44 +-- .../dd_wrapper/src/crashtracker_interface.cpp | 47 +--- ddtrace/settings/crashtracker.py | 19 +- ...rashtracking-zombies-7d84bc2e0b4d366e.yaml | 4 + .../crashtracker/test_crashtracker.py | 251 +++++++++++++++--- tests/internal/crashtracker/utils.py | 65 ++++- tests/telemetry/test_writer.py | 6 +- 15 files changed, 323 insertions(+), 162 deletions(-) create mode 100644 releasenotes/notes/fix-crashtracking-zombies-7d84bc2e0b4d366e.yaml diff --git a/ddtrace/internal/core/crashtracking.py b/ddtrace/internal/core/crashtracking.py index 2f06caf5d0c..87bf9881e28 100644 --- a/ddtrace/internal/core/crashtracking.py +++ b/ddtrace/internal/core/crashtracking.py @@ -37,8 +37,8 @@ def start() -> bool: crashtracker.set_runtime_id(get_runtime_id()) crashtracker.set_runtime_version(platform.python_version()) crashtracker.set_library_version(version.get_version()) - crashtracker.set_alt_stack(bool(crashtracker_config.alt_stack)) - crashtracker.set_wait_for_receiver(bool(crashtracker_config.wait_for_receiver)) + crashtracker.set_create_alt_stack(bool(crashtracker_config.create_alt_stack)) + crashtracker.set_use_alt_stack(bool(crashtracker_config.use_alt_stack)) if crashtracker_config.stacktrace_resolver == "fast": crashtracker.set_resolve_frames_fast() elif crashtracker_config.stacktrace_resolver == "full": diff --git a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake index e5fdfa6bb7f..e713722698b 100644 --- a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake @@ -5,7 +5,7 @@ endif() include(ExternalProject) set(TAG_LIBDATADOG - "v13.1.0" + "v14.0.0" CACHE STRING "libdatadog github tag") set(Datadog_BUILD_DIR ${CMAKE_BINARY_DIR}/libdatadog) diff --git a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt index 6cdf96c58e6..d2e19b88f78 100644 --- a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt +++ b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt @@ -1,5 +1,5 @@ -9cddbc9ece4c2fe9a1f0ab5a7cfed218d617c5154f318e0bce9a6102b265c989 libdatadog-aarch64-alpine-linux-musl.tar.gz -d1f7c6213793bdb520aa78e33a2f4edce187470c7d07cbf21413e954c04bb06f libdatadog-aarch64-apple-darwin.tar.gz -db17a5873d82ef772f969582949b272dcd04044a0cd08b196d3820172a19814d libdatadog-aarch64-unknown-linux-gnu.tar.gz -46d0e6445fa1b0fbe8d079e6fa997fa10a4fef4084fe10f4b5886c92effc7be8 libdatadog-x86_64-alpine-linux-musl.tar.gz -adaf79470fd0b06ce6d63ae8f231e555fa12b70d5bf82565a96a25f59ea8071d libdatadog-x86_64-unknown-linux-gnu.tar.gz +6aa3a1dd9664f1bb51aa64e647344f48deb0b07a2c0c95cfa40af0fd0463cb08 libdatadog-aarch64-alpine-linux-musl.tar.gz +fa29ac61904b0481bcaaf2cc3aff844ac058ce92d0a4d7cfed25e4f178442359 libdatadog-aarch64-apple-darwin.tar.gz +44cde6f2b406842e9e94b36cc04aadfcc628242c634cf103bde2f4907640d39a libdatadog-aarch64-unknown-linux-gnu.tar.gz +0aaed4bbbd30dc77c9e2cd5c9bbc011d101086eb6eada6332f0a8276cd67b691 libdatadog-x86_64-alpine-linux-musl.tar.gz +c88fa1f191637e7e42776d2139721294cebc697d3cc951b972f677bb08d641fd libdatadog-x86_64-unknown-linux-gnu.tar.gz diff --git a/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyi b/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyi index 5df95e5f859..b95c01b65bd 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyi +++ b/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyi @@ -10,8 +10,8 @@ def set_runtime_version(runtime_version: StringType) -> None: ... def set_library_version(profiler_version: StringType) -> None: ... def set_stdout_filename(filename: StringType) -> None: ... def set_stderr_filename(filename: StringType) -> None: ... -def set_alt_stack(alt_stack: bool) -> None: ... -def set_wait_for_receiver(wait: bool) -> None: ... +def set_use_alt_stack(alt_stack: bool) -> None: ... +def set_create_alt_stack(alt_stack: bool) -> None: ... def set_resolve_frames_disable() -> None: ... def set_resolve_frames_fast() -> None: ... def set_resolve_frames_full() -> None: ... diff --git a/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyx b/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyx index 62deb0933c8..1f3420d6c7a 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyx +++ b/ddtrace/internal/datadog/profiling/crashtracker/_crashtracker.pyx @@ -28,8 +28,8 @@ cdef extern from "crashtracker_interface.hpp": void crashtracker_set_library_version(string_view profiler_version) void crashtracker_set_stdout_filename(string_view filename) void crashtracker_set_stderr_filename(string_view filename) - void crashtracker_set_alt_stack(bint alt_stack) - void crashtracker_set_wait_for_receiver(bint wait) + void crashtracker_set_use_alt_stack(bint use_alt_stack) + void crashtracker_set_create_alt_stack(bint create_alt_stack) void crashtracker_set_resolve_frames_disable() void crashtracker_set_resolve_frames_fast() void crashtracker_set_resolve_frames_full() @@ -96,12 +96,12 @@ def set_stderr_filename(filename: StringType) -> None: crashtracker_set_stderr_filename(string_view(filename_bytes, len(filename_bytes))) -def set_alt_stack(alt_stack: bool) -> None: - crashtracker_set_alt_stack(alt_stack) +def set_create_alt_stack(create_alt_stack: bool) -> None: + crashtracker_set_create_alt_stack(create_alt_stack) -def set_wait_for_receiver(wait: bool) -> None: - crashtracker_set_wait_for_receiver(wait) +def set_use_alt_stack(use_alt_stack: bool) -> None: + crashtracker_set_use_alt_stack(use_alt_stack) def set_resolve_frames_disable() -> None: diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/constants.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/constants.hpp index 1467ac6248d..5619de2183d 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/constants.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/constants.hpp @@ -11,8 +11,8 @@ constexpr unsigned int g_default_max_nframes = 64; // their stacks may be silently truncated, which is unfortunate. constexpr unsigned int g_backend_max_nframes = 512; -// Maximum amount of time, in seconds, to wait for crashtracker send operations -constexpr uint64_t g_crashtracker_timeout_secs = 5; +// Maximum amount of time, in milliseconds, to wait for crashtracker signal handler +constexpr uint64_t g_crashtracker_timeout_ms = 5000; // Default value for the max number of samples to keep in the SynchronizedSamplePool constexpr size_t g_default_sample_pool_capacity = 4; diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker.hpp index 02151d422ec..ce362780d58 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker.hpp @@ -31,13 +31,13 @@ struct ProfilingState class Crashtracker { private: - bool create_alt_stack = false; - bool wait_for_receiver = true; + bool create_alt_stack = true; + bool use_alt_stack = true; std::optional stderr_filename{ std::nullopt }; std::optional stdout_filename{ std::nullopt }; std::string path_to_receiver_binary; ddog_crasht_StacktraceCollection resolve_frames = DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS; - uint64_t timeout_secs = g_crashtracker_timeout_secs; + uint64_t timeout_ms = g_crashtracker_timeout_ms; ProfilingState profiling_state; @@ -72,9 +72,9 @@ class Crashtracker void set_library_version(std::string_view _library_version); void set_url(std::string_view _url); void set_tag(std::string_view _key, std::string_view _value); - void set_wait_for_receiver(bool _wait); void set_create_alt_stack(bool _create_alt_stack); + void set_use_alt_stack(bool _use_alt_stack); void set_stderr_filename(std::string_view _stderr_filename); void set_stdout_filename(std::string_view _stdout_filename); bool set_receiver_binary_path(std::string_view _path_to_receiver_binary); @@ -83,7 +83,6 @@ class Crashtracker // Helpers bool start(); - bool atfork_child(); // State transition void sampling_start(); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker_interface.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker_interface.hpp index d4026e26712..e22a5fcc68e 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker_interface.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/crashtracker_interface.hpp @@ -19,8 +19,8 @@ extern "C" void crashtracker_set_library_version(std::string_view profiler_version); void crashtracker_set_stdout_filename(std::string_view filename); void crashtracker_set_stderr_filename(std::string_view filename); - void crashtracker_set_alt_stack(bool alt_stack); - void crashtracker_set_wait_for_receiver(bool wait); + void crashtracker_set_create_alt_stack(bool create_alt_stack); + void crashtracker_set_use_alt_stack(bool use_alt_stack); void crashtracker_set_resolve_frames_disable(); void crashtracker_set_resolve_frames_fast(); void crashtracker_set_resolve_frames_full(); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp index c8bda44eab3..d14c5380e19 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp @@ -13,9 +13,9 @@ Datadog::Crashtracker::set_create_alt_stack(bool _create_alt_stack) } void -Datadog::Crashtracker::set_wait_for_receiver(bool _wait) +Datadog::Crashtracker::set_use_alt_stack(bool _use_alt_stack) { - wait_for_receiver = _wait; + use_alt_stack = _use_alt_stack; } void @@ -125,10 +125,10 @@ Datadog::Crashtracker::get_config() { ddog_crasht_Config config{}; config.create_alt_stack = create_alt_stack; + config.use_alt_stack = use_alt_stack; config.endpoint = ddog_endpoint_from_url(to_slice(url)); config.resolve_frames = resolve_frames; - config.timeout_secs = timeout_secs; - config.wait_for_receiver = wait_for_receiver; + config.timeout_ms = timeout_ms; return config; } @@ -205,7 +205,7 @@ Datadog::Crashtracker::start() auto tags = get_tags(); auto metadata = get_metadata(tags); - auto result = ddog_crasht_init_with_receiver(config, receiver_config, metadata); + auto result = ddog_crasht_init(config, receiver_config, metadata); ddog_Vec_Tag_drop(tags); if (result.tag != DDOG_CRASHT_RESULT_OK) { // NOLINT (cppcoreguidelines-pro-type-union-access) auto err = result.err; // NOLINT (cppcoreguidelines-pro-type-union-access) @@ -217,40 +217,6 @@ Datadog::Crashtracker::start() return true; } -bool -Datadog::Crashtracker::atfork_child() -{ - auto config = get_config(); - auto receiver_config = get_receiver_config(); - auto tags = get_tags(); - auto metadata = get_metadata(tags); - - auto result = ddog_crasht_update_on_fork(config, receiver_config, metadata); - ddog_Vec_Tag_drop(tags); - if (result.tag != DDOG_CRASHT_RESULT_OK) { // NOLINT (cppcoreguidelines-pro-type-union-access) - auto err = result.err; // NOLINT (cppcoreguidelines-pro-type-union-access) - std::string errmsg = err_to_msg(&err, "Error initializing crash tracker"); - std::cerr << errmsg << std::endl; - ddog_Error_drop(&err); - return false; - } - - // Reset the profiling state - profiling_state.is_sampling.store(0); - auto res_sampling = ddog_crasht_end_op(DDOG_CRASHT_OP_TYPES_PROFILER_COLLECTING_SAMPLE); - (void)res_sampling; - - profiling_state.is_unwinding.store(0); - auto res_unwinding = ddog_crasht_end_op(DDOG_CRASHT_OP_TYPES_PROFILER_UNWINDING); - (void)res_unwinding; - - profiling_state.is_serializing.store(0); - auto res_serializing = ddog_crasht_end_op(DDOG_CRASHT_OP_TYPES_PROFILER_SERIALIZING); - (void)res_serializing; - - return true; -} - // Profiling state management void Datadog::Crashtracker::sampling_stop() diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker_interface.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker_interface.cpp index e1dcbb9ecd6..4f7b4ac5ac7 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker_interface.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker_interface.cpp @@ -3,7 +3,6 @@ #include #include -#include #include // If the crashtracker exe target name is not set, then fail @@ -21,12 +20,6 @@ crashtracker_get_exe_name() // cppcheck-suppress unusedFunction return CRASHTRACKER_EXE_TARGET_NAME; } -void -crashtracker_postfork_child() -{ - crashtracker.atfork_child(); -} - void crashtracker_set_url(std::string_view url) // cppcheck-suppress unusedFunction { @@ -88,15 +81,15 @@ crashtracker_set_stderr_filename(std::string_view filename) // cppcheck-suppress } void -crashtracker_set_alt_stack(bool alt_stack) // cppcheck-suppress unusedFunction +crashtracker_set_create_alt_stack(bool create_alt_stack) // cppcheck-suppress unusedFunction { - crashtracker.set_create_alt_stack(alt_stack); + crashtracker.set_create_alt_stack(create_alt_stack); } void -crashtracker_set_wait_for_receiver(bool wait) // cppcheck-suppress unusedFunction +crashtracker_set_use_alt_stack(bool use_alt_stack) // cppcheck-suppress unusedFunction { - crashtracker.set_wait_for_receiver(wait); + crashtracker.set_use_alt_stack(use_alt_stack); } void @@ -135,21 +128,6 @@ crashtracker_set_tag(std::string_view key, std::string_view value) // cppcheck-s crashtracker.set_tag(key, value); } -// Store the old segfault handler (uses sigaction prototype) -void (*old_sigsegv_handler)(int, siginfo_t*, void*) = nullptr; -void (*old_sigbus_handler)(int, siginfo_t*, void*) = nullptr; - -// Trap sigsegv JUST to suppress stderr -void -close_stderr_chainer(int signo, siginfo_t* info, void* context) -{ - if (old_sigsegv_handler) { - close(STDERR_FILENO); - old_sigsegv_handler(signo, info, context); - } - _exit(0); -} - void crashtracker_start() // cppcheck-suppress unusedFunction { @@ -158,23 +136,6 @@ crashtracker_start() // cppcheck-suppress unusedFunction crashtracker.start(); crashtracker_initialized = true; - // v11.0 of crashtracker has a bug where it prints erroneously to stderr - // If any handle is detected on the signals (sigsegv/sigbus) crashtracker attaches to, - // we suppress stderr - struct sigaction sa; - sigaction(SIGSEGV, nullptr, &sa); - old_sigsegv_handler = sa.sa_sigaction; - sa.sa_sigaction = close_stderr_chainer; - sigaction(SIGSEGV, &sa, nullptr); - - // Handle sigbus - sigaction(SIGBUS, nullptr, &sa); - old_sigbus_handler = sa.sa_sigaction; - sa.sa_sigaction = close_stderr_chainer; - sigaction(SIGBUS, &sa, nullptr); - - // Also install the post-fork handler for the child process - pthread_atfork(nullptr, nullptr, crashtracker_postfork_child); return true; }(); (void)initialized; diff --git a/ddtrace/settings/crashtracker.py b/ddtrace/settings/crashtracker.py index 07d838693d8..26340922689 100644 --- a/ddtrace/settings/crashtracker.py +++ b/ddtrace/settings/crashtracker.py @@ -73,13 +73,22 @@ class CrashtrackingConfig(En): help="The destination filename for crashtracking stderr", ) - alt_stack = En.v( + use_alt_stack = En.v( bool, - "alt_stack", - default=False, + "use_alt_stack", + default=True, help_type="Boolean", - help="Whether to use an alternate stack for crashtracking" - "This is generally useful only for dd-trace-py development.", + help="Whether to use an alternate stack for crashtracking.", + ) + + create_alt_stack = En.v( + bool, + "create_alt_stack", + default=True, + help_type="Boolean", + help="Whether to create an alternate stack for crashtracking." + "The Python runtime creates an altstack of very small size, so this parameter is typically combined with" + "use_alt_stack to ensure that the altstack is large enough.", ) _stacktrace_resolver = En.v( diff --git a/releasenotes/notes/fix-crashtracking-zombies-7d84bc2e0b4d366e.yaml b/releasenotes/notes/fix-crashtracking-zombies-7d84bc2e0b4d366e.yaml new file mode 100644 index 00000000000..6e673f1e11f --- /dev/null +++ b/releasenotes/notes/fix-crashtracking-zombies-7d84bc2e0b4d366e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Fixes an issue where the use of the crashtracking component could result in zombie processes. diff --git a/tests/internal/crashtracker/test_crashtracker.py b/tests/internal/crashtracker/test_crashtracker.py index 26dab08381b..049d26e5244 100644 --- a/tests/internal/crashtracker/test_crashtracker.py +++ b/tests/internal/crashtracker/test_crashtracker.py @@ -19,11 +19,11 @@ def test_crashtracker_available(): def test_crashtracker_config(): import pytest - from tests.internal.crashtracker.utils import read_files - from tests.internal.crashtracker.utils import start_crashtracker + from tests.internal.crashtracker.utils import CrashtrackerWrapper - start_crashtracker(1234) - stdout_msg, stderr_msg = read_files(["stdout.log", "stderr.log"]) + ct = CrashtrackerWrapper(1234, "config") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() if stdout_msg or stderr_msg: pytest.fail("contents of stdout.log: %s, stderr.log: %s" % (stdout_msg, stderr_msg)) @@ -31,11 +31,20 @@ def test_crashtracker_config(): @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") @pytest.mark.subprocess() def test_crashtracker_config_bytes(): + import os + import pytest import ddtrace.internal.datadog.profiling.crashtracker as crashtracker from tests.internal.crashtracker.utils import read_files + # Delete the stdout and stderr files if they exist + base_name = b"config_bytes" + stdout, stderr = (f"{base_name}.{x}.log" for x in (b"stdout", b"stderr")) + for file in [stdout, stderr]: + if os.path.exists(file): + os.unlink(file) + try: crashtracker.set_url(b"http://localhost:1234") crashtracker.set_service(b"my_favorite_service") @@ -44,15 +53,14 @@ def test_crashtracker_config_bytes(): crashtracker.set_runtime_version(b"v9001") crashtracker.set_runtime_id(b"0") crashtracker.set_library_version(b"v2.7.1.8") - crashtracker.set_stdout_filename(b"stdout.log") - crashtracker.set_stderr_filename(b"stderr.log") - crashtracker.set_alt_stack(False) + crashtracker.set_stdout_filename(stdout) + crashtracker.set_stderr_filename(stderr) crashtracker.set_resolve_frames_full() assert crashtracker.start() except Exception: pytest.fail("Exception when starting crashtracker") - stdout_msg, stderr_msg = read_files(["stdout.log", "stderr.log"]) + stdout_msg, stderr_msg = read_files([stdout, stderr]) if stdout_msg or stderr_msg: pytest.fail("contents of stdout.log: %s, stderr.log: %s" % (stdout_msg, stderr_msg)) @@ -63,17 +71,16 @@ def test_crashtracker_started(): import pytest import ddtrace.internal.datadog.profiling.crashtracker as crashtracker - from tests.internal.crashtracker.utils import read_files + from tests.internal.crashtracker.utils import CrashtrackerWrapper try: - crashtracker.set_stdout_filename("stdout.log") - crashtracker.set_stderr_filename("stderr.log") - assert crashtracker.start() - assert crashtracker.is_started() + ct = CrashtrackerWrapper(1234, "started") + assert ct.start() + assert crashtracker.is_started() # Confirmation at the module level except Exception: pytest.fail("Exception when starting crashtracker") - stdout_msg, stderr_msg = read_files(["stdout.log", "stderr.log"]) + stdout_msg, stderr_msg = ct.logs() if stdout_msg or stderr_msg: pytest.fail("contents of stdout.log: %s, stderr.log: %s" % (stdout_msg, stderr_msg)) @@ -100,13 +107,14 @@ def test_crashtracker_simple(): # Part 3 and 4, Fork, setup crashtracker, and crash pid = os.fork() if pid == 0: - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) - assert not stdout_msg - assert not stderr_msg + ct = utils.CrashtrackerWrapper(port, "simple") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() + assert not stdout_msg, stdout_msg + assert not stderr_msg, stderr_msg ctypes.string_at(0) - exit(-1) + sys.exit(-1) # Part 5 # Check to see if the listening socket was triggered, if so accept the connection @@ -137,8 +145,9 @@ def test_crashtracker_simple_fork(): assert sock # Part 3, setup crashtracker in parent - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "simple_fork") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg @@ -146,7 +155,7 @@ def test_crashtracker_simple_fork(): pid = os.fork() if pid == 0: ctypes.string_at(0) - exit(-1) # just in case + sys.exit(-1) # just in case # Part 5, check conn = utils.listen_get_conn(sock) @@ -183,10 +192,11 @@ def test_crashtracker_simple_sigbus(): assert sock # Part 3, setup crashtracker in parent - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) - assert not stdout_msg - assert not stderr_msg + ct = utils.CrashtrackerWrapper(port, "simple_sigbus") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() + assert not stdout_msg, stdout_msg + assert not stderr_msg, stderr_msg # Part 4, Fork and crash pid = os.fork() @@ -202,7 +212,7 @@ def test_crashtracker_simple_sigbus(): arr_type = ctypes.POINTER(ctypes.c_char * 4096) arr = ctypes.cast(mm, arr_type).contents arr[4095] = b"x" # sigbus - exit(-1) # just in case + sys.exit(-1) # just in case # Part 5, check conn = utils.listen_get_conn(sock) @@ -226,8 +236,9 @@ def test_crashtracker_raise_sigsegv(): assert port assert sock - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "raise_sigsegv") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg @@ -235,7 +246,7 @@ def test_crashtracker_raise_sigsegv(): pid = os.fork() if pid == 0: os.kill(os.getpid(), signal.SIGSEGV.value) - exit(-1) + sys.exit(-1) # Part 5, check conn = utils.listen_get_conn(sock) @@ -259,8 +270,9 @@ def test_crashtracker_raise_sigbus(): assert port assert sock - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "raise_sigbus") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg @@ -268,7 +280,7 @@ def test_crashtracker_raise_sigbus(): pid = os.fork() if pid == 0: os.kill(os.getpid(), signal.SIGBUS.value) - exit(-1) + sys.exit(-1) # Part 5, check conn = utils.listen_get_conn(sock) @@ -281,8 +293,9 @@ def test_crashtracker_raise_sigbus(): preload_code = """ import ctypes +import sys ctypes.string_at(0) -exit(-1) +sys.exit(-1) """ @@ -337,7 +350,7 @@ def test_crashtracker_preload_disabled(ddtrace_run_python_code_in_subprocess): import ctypes import ddtrace.auto ctypes.string_at(0) -exit(-1) +sys.exit(-1) """ @@ -429,13 +442,14 @@ def test_crashtracker_tags_required(): pid = os.fork() if pid == 0: - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "tags_required") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg ctypes.string_at(0) - exit(-1) + sys.exit(-1) conn = utils.listen_get_conn(sock) assert conn @@ -515,13 +529,14 @@ def test_crashtracker_user_tags_profiling(): # Set the tags before starting for k, v in tags.items(): crashtracker.set_tag(k, v) - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "user_tags_profiling") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg ctypes.string_at(0) - exit(-1) + sys.exit(-1) conn = utils.listen_get_conn(sock) assert conn @@ -561,13 +576,14 @@ def test_crashtracker_user_tags_core(): # Set the tags before starting for k, v in tags.items(): crashtracking.add_tag(k, v) - assert utils.start_crashtracker(port) - stdout_msg, stderr_msg = utils.read_files(["stdout.log", "stderr.log"]) + ct = utils.CrashtrackerWrapper(port, "user_tags_core") + assert ct.start() + stdout_msg, stderr_msg = ct.logs() assert not stdout_msg assert not stderr_msg ctypes.string_at(0) - exit(-1) + sys.exit(-1) conn = utils.listen_get_conn(sock) assert conn @@ -579,3 +595,150 @@ def test_crashtracker_user_tags_core(): for k, v in tags.items(): assert k.encode() in data assert v.encode() in data + + +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +@pytest.mark.subprocess() +def test_crashtracker_echild_hang(): + """ + It's possible for user code and services to harvest child processes by doing a `waitpid()` until errno is ECHILD. + Although this is a more common pattern for native code, because the crashtracking receiver could suppress this + condition, we test for it. + """ + import ctypes + import os + import random + import sys + import time + + import tests.internal.crashtracker.utils as utils + + # Create a port and listen on it + port, sock = utils.crashtracker_receiver_bind() + assert port + assert sock + + # We're going to create a lot of child processes and we're not going to care about whether they successfully send + # crashtracking data. Accordingly, we create a file--children will append here if they run into an unwanted + # condition, and we'll check it at the end. + err_file = "/tmp/echild_error.log" + + # Set this process as a subreaper, since we want deparented children to be visible to us + # (this emulates the behavior of a service which is PID 1 in a container) + utils.set_cerulean_mollusk() + + # Fork, setup crashtracking in the child. + # The child process emulates a worker fork in the sense that we spawn a number of them in the parent and then + # do a timed `waitpid()` anticipating ECHILD until they all exit. + children = [] + for _ in range(5): + pid = os.fork() + if pid == 0: + rand_num = random.randint(0, 999999) + base_name = f"echild_hang_{rand_num}" + ct = utils.CrashtrackerWrapper(port, base_name) + if not ct.start(): + with open(err_file, "a") as f: + f.write("X") + sys.exit(-1) + + stdout_msg, stderr_msg = ct.logs() + if not stdout_msg or not stderr_msg: + with open(err_file, "a") as f: + f.write("X") + sys.exit(-1) + + # Crashtracking is started. Let's sleep for 100ms to give the parent a chance to do some stuff, + # then crash. + time.sleep(0.1) + + ctypes.string_at(0) + sys.exit(-1) + else: + children.append(pid) + + # Wait for all children to exit. It shouldn't take more than 1s, so fail if it does. + timeout = 1 # seconds + end_time = time.time() + timeout + while True: + if time.time() > end_time: + pytest.fail("Timed out waiting for children to exit") + try: + _, __ = os.waitpid(-1, os.WNOHANG) + except ChildProcessError: + break + except Exception as e: + pytest.fail("Unexpected exception: %s" % e) + + +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +@pytest.mark.subprocess() +def test_crashtracker_no_zombies(): + """ + If a process has been designated as the reaper for another process (either because it is the parent, it is marked + as the init process for the given PID namespace, or it has been set as a subreaper), then it is responsible for + harvesting the return status of its children. If this is not done, then the entry is never removed from the + process table for the terminated PID. This is often not a problem, but we should still avoid unbounded resource + leaks. + """ + import ctypes + import os + import random + import sys + import time + + import tests.internal.crashtracker.utils as utils + + # Create a port and listen on it + # This doesn't matter, but we don't need spurious errors in crashtracking now. + port, sock = utils.crashtracker_receiver_bind() + assert port + assert sock + + err_file = "/tmp/zombie_error.log" + + # Set this process as a subreaper, since we want deparented children to be visible to us + # (this emulates the behavior of a service which is PID 1 in a container) + utils.set_cerulean_mollusk() + + # This is a rapid fan-out procedure. We do a combination of terminations, aborts, segfaults, etc., hoping to elicit + # zombies. + children = [] + for _ in range(5): + pid = os.fork() + if pid == 0: + rand_num = random.randint(0, 999999) + base_name = f"no_zombies_{rand_num}" + ct = utils.CrashtrackerWrapper(port, base_name) + if not ct.start(): + with open(err_file, "a") as f: + f.write("X") + sys.exit(-1) + + stdout_msg, stderr_msg = ct.logs() + if not stdout_msg or not stderr_msg: + with open(err_file, "a") as f: + f.write("X") + sys.exit(-1) + + # Crashtracking is started. Let's sleep for 100ms to give the parent a chance to do some stuff, + # then crash. + time.sleep(0.1) + + ctypes.string_at(0) + sys.exit(-1) + else: + children.append(pid) + + # Wait for all children to exit. It shouldn't take more than 1s, so fail if it does. + timeout = 1 # seconds + end_time = time.time() + timeout + while True: + if time.time() > end_time: + pytest.fail("Timed out waiting for children to exit") + try: + _, __ = os.waitpid(-1, os.WNOHANG) + except ChildProcessError: + break + except Exception as e: + pytest.fail("Unexpected exception: %s" % e) diff --git a/tests/internal/crashtracker/utils.py b/tests/internal/crashtracker/utils.py index 1a742fbe502..17753e24ea9 100644 --- a/tests/internal/crashtracker/utils.py +++ b/tests/internal/crashtracker/utils.py @@ -49,7 +49,7 @@ def conn_to_bytes(conn): return ret -def start_crashtracker(port: int): +def start_crashtracker(port: int, stdout=None, stderr=None): """Start the crashtracker with some placeholder values""" ret = False try: @@ -62,9 +62,8 @@ def start_crashtracker(port: int): crashtracker.set_runtime_version("v9001") crashtracker.set_runtime_id("0") crashtracker.set_library_version("v2.7.1.8") - crashtracker.set_stdout_filename("stdout.log") - crashtracker.set_stderr_filename("stderr.log") - crashtracker.set_alt_stack(False) + crashtracker.set_stdout_filename(stdout) + crashtracker.set_stderr_filename(stderr) crashtracker.set_resolve_frames_full() ret = crashtracker.start() except Exception as e: @@ -82,3 +81,61 @@ def read_files(files): this_msg = f.read() msg.append(this_msg) return msg + + +def set_cerulean_mollusk(): + """ + Many crashtracking tests deal with the behavior of the init process in a given PID namespace. + For testing, it's useful to designate a process as the subreaper via `PR_SET_CHILD_SUBREAPER`. + This function sets the current process as the subreaper. + There is no need to fear this function. + """ + try: + import ctypes + import ctypes.util + + libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) + PR_SET_CHILD_SUBREAPER = 36 # from + + # Now setup the prctl definition + libc.prctl.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong] + libc.prctl.restype = ctypes.c_int + + result = libc.prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) + if result != 0: + return False + except Exception as e: + print("Failed to set subreaper: %s" % str(e)) + return False + return True + + +# A class that wraps start_crashtracker and maintains its own logfiles +class CrashtrackerWrapper: + _seed = 0 + + def __init__(self, port: int, base_name=""): + if CrashtrackerWrapper._seed == 0: + CrashtrackerWrapper._seed = random.randint(0, 999999) + + self.port = port + self.stdout = f"stdout.{base_name}.{CrashtrackerWrapper._seed}.log" + self.stderr = f"stderr.{base_name}.{CrashtrackerWrapper._seed}.log" + + for file in [self.stdout, self.stderr]: + if os.path.exists(file): + os.unlink(file) + + def __del__(self): + for file in [self.stdout, self.stderr]: + if os.path.exists(file): + os.unlink(file) + + def get_filenames(self): + return [self.stdout, self.stderr] + + def start(self): + return start_crashtracker(self.port, self.stdout, self.stderr) + + def logs(self): + return read_files([self.stdout, self.stderr]) diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index df107e03172..8bb6f9ced4a 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -153,7 +153,8 @@ def test_app_started_event(telemetry_writer, test_agent_session, mock_time): {"name": "profiling_enabled", "origin": "default", "value": "false"}, {"name": "data_streams_enabled", "origin": "default", "value": "false"}, {"name": "appsec_enabled", "origin": "default", "value": "false"}, - {"name": "crashtracking_alt_stack", "origin": "unknown", "value": False}, + {"name": "crashtracking_create_alt_stack", "origin": "unknown", "value": True}, + {"name": "crashtracking_use_alt_stack", "origin": "unknown", "value": True}, {"name": "crashtracking_available", "origin": "unknown", "value": sys.platform == "linux"}, {"name": "crashtracking_debug_url", "origin": "unknown", "value": None}, {"name": "crashtracking_enabled", "origin": "unknown", "value": sys.platform == "linux"}, @@ -325,13 +326,14 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED", "origin": "default", "value": True}, {"name": "DD_CIVISIBILITY_ITR_ENABLED", "origin": "default", "value": True}, {"name": "DD_CIVISIBILITY_LOG_LEVEL", "origin": "default", "value": "info"}, - {"name": "DD_CRASHTRACKING_ALT_STACK", "origin": "default", "value": False}, + {"name": "DD_CRASHTRACKING_CREATE_ALT_STACK", "origin": "default", "value": True}, {"name": "DD_CRASHTRACKING_DEBUG_URL", "origin": "default", "value": None}, {"name": "DD_CRASHTRACKING_ENABLED", "origin": "default", "value": True}, {"name": "DD_CRASHTRACKING_STACKTRACE_RESOLVER", "origin": "default", "value": "full"}, {"name": "DD_CRASHTRACKING_STDERR_FILENAME", "origin": "default", "value": None}, {"name": "DD_CRASHTRACKING_STDOUT_FILENAME", "origin": "default", "value": None}, {"name": "DD_CRASHTRACKING_TAGS", "origin": "default", "value": ""}, + {"name": "DD_CRASHTRACKING_USE_ALT_STACK", "origin": "default", "value": True}, {"name": "DD_CRASHTRACKING_WAIT_FOR_RECEIVER", "origin": "default", "value": True}, {"name": "DD_DATA_STREAMS_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_DJANGO_INCLUDE_USER_EMAIL", "origin": "default", "value": False}, From 5a990f706ef2c0e2da7d8dd5698297fa7c4db2dc Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 8 Nov 2024 12:15:21 +0100 Subject: [PATCH 127/372] chore(ci): update codespell (#11209) Codespell 2.3.0 introduces a new feature `# codespell:ignore` tag to skip lines, but if we update to the latest version codespell reports many typos: https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/76135/workflows/2caae2b6-b77f-401c-bb27-03d65d324241/jobs/4343027 This PR updates codespell and fix that typos ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_ast/ast_patching.py | 2 +- ddtrace/appsec/_iast/_taint_tracking/__init__.py | 2 +- ddtrace/appsec/_iast/_taint_utils.py | 2 +- ddtrace/bootstrap/sitecustomize.py | 2 +- ddtrace/contrib/internal/django/utils.py | 2 +- ddtrace/contrib/internal/rediscluster/patch.py | 2 +- ddtrace/internal/_encoding.pyx | 2 +- ddtrace/internal/ci_visibility/api/_module.py | 2 +- ddtrace/internal/datadog/profiling/dd_wrapper/dd_wrapper.md | 2 +- ddtrace/internal/opentelemetry/context.py | 2 +- ddtrace/profiling/profiler.py | 2 +- ddtrace/settings/asm.py | 2 +- ddtrace/settings/config.py | 2 +- docs/spelling_wordlist.txt | 6 ++++++ hatch.toml | 4 ++-- .../reconcile-sampling-decsions-otel-21c6aae6ccbcfa31.yaml | 2 +- tests/contrib/boto/test.py | 2 +- tests/contrib/botocore/test_bedrock_llmobs.py | 2 +- tests/contrib/dramatiq/test_integration.py | 2 +- tests/contrib/graphql/test_graphql.py | 4 ++-- tests/integration/test_sampling.py | 2 +- tests/internal/test_module.py | 2 +- tests/internal/test_tracer_flare.py | 2 +- tests/opentracer/core/test_tracer.py | 6 +++--- tests/tracer/test_propagation.py | 4 ++-- tests/tracer/test_sampler.py | 2 +- 26 files changed, 36 insertions(+), 30 deletions(-) diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 257b32923f6..6dbad8752b6 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -340,7 +340,7 @@ def _in_python_stdlib(module_name: str) -> bool: def _should_iast_patch(module_name: Text) -> bool: """ - select if module_name should be patch from the longuest prefix that match in allow or deny list. + select if module_name should be patch from the longest prefix that match in allow or deny list. if a prefix is in both list, deny is selected. """ # TODO: A better solution would be to migrate the original algorithm to C++: diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 7c6d12cecf2..00d676758fa 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -148,7 +148,7 @@ def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, sou if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return pyobject - # We need this validation in different contition if pyobject is not a text type and creates a side-effect such as + # We need this validation in different condition if pyobject is not a text type and creates a side-effect such as # __len__ magic method call. pyobject_len = 0 if isinstance(pyobject, IAST.TEXT_TYPES): diff --git a/ddtrace/appsec/_iast/_taint_utils.py b/ddtrace/appsec/_iast/_taint_utils.py index 3927349e176..8b5e1b97caa 100644 --- a/ddtrace/appsec/_iast/_taint_utils.py +++ b/ddtrace/appsec/_iast/_taint_utils.py @@ -117,7 +117,7 @@ def taint_structure(main_obj, source_key, source_value, override_pyobject_tainte elif isinstance(command.obj, abc.Mapping): res = {} stack.append(command.post(res)) - # use dict fondamental enumeration if possible to bypass any override of custom classes + # use dict fundamental enumeration if possible to bypass any override of custom classes iterable = dict.items(command.obj) if isinstance(command.obj, dict) else command.obj.items() todo = [] for k, v in list(iterable): diff --git a/ddtrace/bootstrap/sitecustomize.py b/ddtrace/bootstrap/sitecustomize.py index b8c37ec4129..46001bccd8d 100644 --- a/ddtrace/bootstrap/sitecustomize.py +++ b/ddtrace/bootstrap/sitecustomize.py @@ -88,7 +88,7 @@ def drop(module_name): drop(m) - # TODO: The better strategy is to identify the core modues in LOADED_MODULES + # TODO: The better strategy is to identify the core modules in LOADED_MODULES # that should not be unloaded, and then unload as much as possible. UNLOAD_MODULES = frozenset( [ diff --git a/ddtrace/contrib/internal/django/utils.py b/ddtrace/contrib/internal/django/utils.py index c1ce819bae1..14f5e3dd4e6 100644 --- a/ddtrace/contrib/internal/django/utils.py +++ b/ddtrace/contrib/internal/django/utils.py @@ -194,7 +194,7 @@ def _set_resolver_tags(pin, span, request): if hasattr(resolver_match[0], "view_class"): # In django==4.0, view.__name__ defaults to .views.view - # Accessing view.view_class is equired for django>4.0 to get the name of underlying view + # Accessing view.view_class is required for django>4.0 to get the name of underlying view handler = func_name(resolver_match[0].view_class) else: handler = func_name(resolver_match[0]) diff --git a/ddtrace/contrib/internal/rediscluster/patch.py b/ddtrace/contrib/internal/rediscluster/patch.py index 9acbfd06173..a415096ef10 100644 --- a/ddtrace/contrib/internal/rediscluster/patch.py +++ b/ddtrace/contrib/internal/rediscluster/patch.py @@ -27,7 +27,7 @@ # DEV: In `2.0.0` `__version__` is a string and `VERSION` is a tuple, -# but in `1.x.x` `__version__` is a tuple annd `VERSION` does not exist +# but in `1.x.x` `__version__` is a tuple and `VERSION` does not exist REDISCLUSTER_VERSION = getattr(rediscluster, "VERSION", rediscluster.__version__) config._add( diff --git a/ddtrace/internal/_encoding.pyx b/ddtrace/internal/_encoding.pyx index 96de99d915b..f85fe7c6776 100644 --- a/ddtrace/internal/_encoding.pyx +++ b/ddtrace/internal/_encoding.pyx @@ -570,7 +570,7 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): for link in span_links: # SpanLink.to_dict() returns all serializable span link fields - # v0.4 encoding is disabled by default. SpanLinks.to_dict() is optimizied for the v0.5 format. + # v0.4 encoding is disabled by default. SpanLinks.to_dict() is optimized for the v0.5 format. d = link.to_dict() # Encode 128 bit trace ids usings two 64bit integers tid = int(d["trace_id"][:16], 16) diff --git a/ddtrace/internal/ci_visibility/api/_module.py b/ddtrace/internal/ci_visibility/api/_module.py index 1679d4bd806..306f391ec3a 100644 --- a/ddtrace/internal/ci_visibility/api/_module.py +++ b/ddtrace/internal/ci_visibility/api/_module.py @@ -46,7 +46,7 @@ def _get_hierarchy_tags(self) -> Dict[str, str]: module_path: str if self._module_path: if self._module_path == self._session_settings.workspace_path: - # '.' is not the desired relative path when the worspace and module path are the same + # '.' is not the desired relative path when the workspace and module path are the same module_path = "" elif self._module_path.is_relative_to(self._session_settings.workspace_path): module_path = str(self._module_path.relative_to(self._session_settings.workspace_path)) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/dd_wrapper.md b/ddtrace/internal/datadog/profiling/dd_wrapper/dd_wrapper.md index e9b510d7e5c..6411910ffdb 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/dd_wrapper.md +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/dd_wrapper.md @@ -42,7 +42,7 @@ This is very similar to the lock profiler, except for allocations. Samplers might interrupt each other on a single thread. Since the v1 stack sampler is written in Python, it may request allocations. -In turn, those alocations may trigger the memory sampler. +In turn, those allocations may trigger the memory sampler. This requires the caller of the library to specify what kind of sample they are taking. diff --git a/ddtrace/internal/opentelemetry/context.py b/ddtrace/internal/opentelemetry/context.py index edea6245b1a..0817f5d1da2 100644 --- a/ddtrace/internal/opentelemetry/context.py +++ b/ddtrace/internal/opentelemetry/context.py @@ -37,7 +37,7 @@ def attach(self, otel_context): self._ddcontext_provider.activate(ddcontext) else: log.error( - "Programming ERROR: ddtrace does not support activiting spans with the type: %s. Please open a " + "Programming ERROR: ddtrace does not support activating spans with the type: %s. Please open a " "github issue at: https://github.com/Datadog/dd-trace-py and set DD_TRACE_OTEL_ENABLED=True.", type(otel_span), ) diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index dc106b17de8..a74136912cc 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -370,7 +370,7 @@ def _collectors_snapshot(self): for events in snapshot: self._recorder.push_events(events) except Exception: - LOG.error("Error while snapshoting collector %r", c, exc_info=True) + LOG.error("Error while snapshotting collector %r", c, exc_info=True) _COPY_IGNORE_ATTRIBUTES = {"status"} diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 26289209d35..f508bf10eb9 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -240,7 +240,7 @@ def __init__(self): self._api_security_enabled = False def reset(self): - """For testing puposes, reset the configuration to its default values given current environment variables.""" + """For testing purposes, reset the configuration to its default values given current environment variables.""" self.__init__() def _eval_asm_can_be_enabled(self): diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 1c613f6a1f5..8af5aa9b2d6 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -411,7 +411,7 @@ def __init__(self): # is deprecated. We should always encourage users to set DD_TRACE_SAMPLING_RULES instead. log.warning( "DD_TRACE_RATE_LIMIT is set to %s and DD_TRACE_SAMPLING_RULES is not set. " - "Tracer rate limitting is only applied to spans that match tracer sampling rules. " + "Tracer rate limiting is only applied to spans that match tracer sampling rules. " "All other spans will be rate limited by the Datadog Agent via DD_APM_MAX_TPS.", rate_limit, ) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index b27c2752aab..cb88fa64a33 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -289,3 +289,9 @@ hotspot CMake libdatadog importlib +oce +assertIn +# tests/contrib/openai/test_openai_v1.py +Nam +# docs/configuration.rst +uest \ No newline at end of file diff --git a/hatch.toml b/hatch.toml index b7ed9ce6097..ba442662416 100644 --- a/hatch.toml +++ b/hatch.toml @@ -5,7 +5,7 @@ dependencies = [ "black==23.10.1", "click==8.1.7", "cython-lint==0.15.0", - "codespell==2.1.0", + "codespell==2.3.0", "bandit==1.7.5", "mypy==0.991", "coverage==7.3.0", @@ -56,7 +56,7 @@ checks = [ "suitespec-check", ] spelling = [ - "codespell --skip='ddwaf.h,*cassettes*' {args:ddtrace/ tests/ releasenotes/ docs/}", + "codespell -I docs/spelling_wordlist.txt --skip='ddwaf.h,*cassettes*,tests/tracer/fixtures/urls.txt' {args:ddtrace/ tests/ releasenotes/ docs/}", ] typing = [ "mypy {args}", diff --git a/releasenotes/notes/reconcile-sampling-decsions-otel-21c6aae6ccbcfa31.yaml b/releasenotes/notes/reconcile-sampling-decsions-otel-21c6aae6ccbcfa31.yaml index 348f17468eb..9e0af11551b 100644 --- a/releasenotes/notes/reconcile-sampling-decsions-otel-21c6aae6ccbcfa31.yaml +++ b/releasenotes/notes/reconcile-sampling-decsions-otel-21c6aae6ccbcfa31.yaml @@ -1,4 +1,4 @@ --- fixes: - | - otel: Ensures all otel sampling decisions are consistent with Datadog Spans. This prevents otel spans in a distrbuted trace from being sampled differently than Datadog spans in the same trace. \ No newline at end of file + otel: Ensures all otel sampling decisions are consistent with Datadog Spans. This prevents otel spans in a distributed trace from being sampled differently than Datadog spans in the same trace. \ No newline at end of file diff --git a/tests/contrib/boto/test.py b/tests/contrib/boto/test.py index 4ca383df30e..f0b9cb57fd1 100644 --- a/tests/contrib/boto/test.py +++ b/tests/contrib/boto/test.py @@ -721,7 +721,7 @@ def test_schematized_operation_name_sts_client(self): False, ( "Test to reproduce the case where args sent to patched function are None," - "can't be mocked: needs AWS crendentials" + "can't be mocked: needs AWS credentials" ), ) def test_elasticache_client(self): diff --git a/tests/contrib/botocore/test_bedrock_llmobs.py b/tests/contrib/botocore/test_bedrock_llmobs.py index b7e80651f35..ced2dc37265 100644 --- a/tests/contrib/botocore/test_bedrock_llmobs.py +++ b/tests/contrib/botocore/test_bedrock_llmobs.py @@ -129,7 +129,7 @@ def _test_llmobs_invoke(cls, provider, bedrock_client, mock_llmobs_span_writer, with get_request_vcr().use_cassette(cassette_name): body, model = json.dumps(body), _MODELS[provider] if provider == "anthropic_message": - # we do this to re-use a cassette which tests + # we do this to reuse a cassette which tests # cross-region inference model = "us." + model response = bedrock_client.invoke_model(body=body, modelId=model) diff --git a/tests/contrib/dramatiq/test_integration.py b/tests/contrib/dramatiq/test_integration.py index 6a8e7da1453..2897cc7edcf 100644 --- a/tests/contrib/dramatiq/test_integration.py +++ b/tests/contrib/dramatiq/test_integration.py @@ -101,7 +101,7 @@ def test_send_exception(self): def fn_task(a: int, b: int) -> int: return a + b - # send() with invalid pararms + # send() with invalid params with pytest.raises(TypeError): fn_task.send_with_options([]) diff --git a/tests/contrib/graphql/test_graphql.py b/tests/contrib/graphql/test_graphql.py index ce4620bc70d..67c5befd7fe 100644 --- a/tests/contrib/graphql/test_graphql.py +++ b/tests/contrib/graphql/test_graphql.py @@ -124,7 +124,7 @@ def test_graphql_with_document_with_no_location(test_schema, test_source_str): @snapshot(token_override="tests.contrib.graphql.test_graphql.test_graphql") -@pytest.mark.skipif(graphql_version < (3, 0), reason="graphql.graphql_sync is NOT suppoerted in v2.0") +@pytest.mark.skipif(graphql_version < (3, 0), reason="graphql.graphql_sync is NOT supported in v2.0") def test_graphql_sync(test_schema, test_source_str): result = graphql.graphql_sync(test_schema, test_source_str) assert result.data == {"hello": "friend"} @@ -159,7 +159,7 @@ def test_graphql_execute_sync_with_middlware_manager( @pytest.mark.snapshot -@pytest.mark.skipif(graphql_version < (3, 0), reason="graphql.graphql_sync is NOT suppoerted in v2.0") +@pytest.mark.skipif(graphql_version < (3, 0), reason="graphql.graphql_sync is NOT supported in v2.0") @pytest.mark.parametrize( "service_name, schema_version", [(None, None), ("my-service", None), (None, "v0"), ("my-service", "v0"), (None, "v1"), ("my-service", "v1")], diff --git a/tests/integration/test_sampling.py b/tests/integration/test_sampling.py index 04749e080ba..bb0d421a2d1 100644 --- a/tests/integration/test_sampling.py +++ b/tests/integration/test_sampling.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent") -RESOURCE = "mycoolre$ource" +RESOURCE = "mycoolre$ource" # codespell:ignore TAGS = {"tag1": "mycooltag"} diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index 68261331f7b..2d27899ef3b 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -569,7 +569,7 @@ def __getattr__(name): assert missing_deprecations == set( [ # Note: The following ddtrace.contrib modules are expected to be part of the public API - # TODO: Revist whether integration utils should be part of the public API + # TODO: Revisit whether integration utils should be part of the public API "ddtrace.contrib.redis_utils", "ddtrace.contrib.trace_utils", "ddtrace.contrib.trace_utils_async", diff --git a/tests/internal/test_tracer_flare.py b/tests/internal/test_tracer_flare.py index 02a9684d3d5..6a492411939 100644 --- a/tests/internal/test_tracer_flare.py +++ b/tests/internal/test_tracer_flare.py @@ -185,7 +185,7 @@ def handle_agent_task(flare: Flare): def test_multiple_process_partial_failure(self): """ - Validte that even if the tracer flare fails for one process, we should + Validate that even if the tracer flare fails for one process, we should still continue the work for the other processes (ensure best effort) """ processes = [] diff --git a/tests/opentracer/core/test_tracer.py b/tests/opentracer/core/test_tracer.py index 34d32c9aedf..c96339ed0e3 100644 --- a/tests/opentracer/core/test_tracer.py +++ b/tests/opentracer/core/test_tracer.py @@ -50,7 +50,7 @@ def test_multiple_tracer_configs(self): def test_invalid_config_key(self): """A config with an invalid key should raise a ConfigException.""" - config = {"enabeld": False} + config = {"enabeld": False} # codespell:ignore # No debug flag should not raise an error tracer = Tracer(service_name="mysvc", config=config) @@ -59,14 +59,14 @@ def test_invalid_config_key(self): config["debug"] = True with pytest.raises(ConfigException) as ce_info: tracer = Tracer(config=config) - assert "enabeld" in str(ce_info) + assert "enabeld" in str(ce_info) # codespell:ignore assert tracer is not None # Test with multiple incorrect keys config["setttings"] = {} with pytest.raises(ConfigException) as ce_info: tracer = Tracer(service_name="mysvc", config=config) - assert ["enabeld", "setttings"] in str(ce_info) + assert ["enabeld", "setttings"] in str(ce_info) # codespell:ignore assert tracer is not None def test_ddtrace_fallback_config(self): diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index d47fef3652d..e4bf9be6ef3 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -284,7 +284,7 @@ def test_extract(tracer): # noqa: F811 "x-datadog-origin": "synthetics", "x-datadog-tags": "_dd.p.test=value,any=tag", "ot-baggage-key1": "value1", - "baggage": "foo=bar,racoon=cute,serverNode=DF%2028", + "baggage": "foo=bar,raccoon=cute,serverNode=DF%2028", } context = HTTPPropagator.extract(headers) @@ -312,7 +312,7 @@ def test_extract(tracer): # noqa: F811 "_dd.p.test": "value", } assert context.get_baggage_item("foo") == "bar" - assert context.get_baggage_item("racoon") == "cute" + assert context.get_baggage_item("raccoon") == "cute" assert context.get_baggage_item("serverNode") == "DF 28" assert len(context.get_all_baggage_items()) == 3 diff --git a/tests/tracer/test_sampler.py b/tests/tracer/test_sampler.py index fcb4448726d..bf86dacef0a 100644 --- a/tests/tracer/test_sampler.py +++ b/tests/tracer/test_sampler.py @@ -677,7 +677,7 @@ def test_sampling_rule_sample_rate_0(): @pytest.mark.subprocess( env={"DD_TRACE_RATE_LIMIT": "2", "DD_TRACE_SAMPLING_RULES": ""}, err=b"DD_TRACE_RATE_LIMIT is set to 2 and DD_TRACE_SAMPLING_RULES is not set. " - b"Tracer rate limitting is only applied to spans that match tracer sampling rules. " + b"Tracer rate limiting is only applied to spans that match tracer sampling rules. " b"All other spans will be rate limited by the Datadog Agent via DD_APM_MAX_TPS.\n", ) def test_rate_limit_without_sampling_rules_warning(): From 2ae085bfe46091c0f780cde698c9dc078a0f8f7e Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Fri, 8 Nov 2024 15:04:01 +0000 Subject: [PATCH 128/372] feat(lib-injection): add SSI denylist using requirements.json (#11306) --- .gitlab-ci.yml | 7 +++++ .gitlab/requirements_allow.json | 10 +++++++ .gitlab/requirements_block.json | 11 ++++++++ lib-injection/sources/requirements.json | 28 +++++++++++++++++++ ...dd-requirements-json-038073d722697e32.yaml | 4 +++ 5 files changed, 60 insertions(+) create mode 100644 .gitlab/requirements_allow.json create mode 100644 .gitlab/requirements_block.json create mode 100644 lib-injection/sources/requirements.json create mode 100644 releasenotes/notes/add-requirements-json-038073d722697e32.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a352f7f72ec..fa04948a8e3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,6 +32,13 @@ include: - local: ".gitlab/dogfood.yml" - local: ".gitlab/release.yml" +requirements_json_test: + rules: + - when: on_success + variables: + REQUIREMENTS_BLOCK_JSON_PATH: ".gitlab/requirements_block.json" + REQUIREMENTS_ALLOW_JSON_PATH: ".gitlab/requirements_allow.json" + package-oci: needs: [ download_dependency_wheels, download_ddtrace_artifacts ] diff --git a/.gitlab/requirements_allow.json b/.gitlab/requirements_allow.json new file mode 100644 index 00000000000..e75807aedfc --- /dev/null +++ b/.gitlab/requirements_allow.json @@ -0,0 +1,10 @@ +[ + {"name": "min glibc x64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"name": "ok glibc x64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.23"}}, + {"name": "high glibc x64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:3.0"}}, + {"name": "musl x64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "musl:1.2.2"}}, + {"name": "min glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.17"}}, + {"name": "ok glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.23"}}, + {"name": "high glibc arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:3.0"}}, + {"name": "musl arm64", "filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "musl:1.2.2"}} + ] \ No newline at end of file diff --git a/.gitlab/requirements_block.json b/.gitlab/requirements_block.json new file mode 100644 index 00000000000..ee655a78b1d --- /dev/null +++ b/.gitlab/requirements_block.json @@ -0,0 +1,11 @@ +[ + {"name": "unsupported 2.x glibc x64","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.16"}}, + {"name": "unsupported 1.x glibc x64","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:1.22"}}, + {"name": "unsupported 2.x.x glibc x64","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.16.9"}}, + {"name": "unsupported 2.x glibc arm64","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.15"}}, + {"name": "unsupported 2.x.x glibc x64","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.14.9"}}, + {"name": "glibx x86","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x86", "libc": "glibc:2.23"}}, + {"name": "musl x86","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "x86", "libc": "musl:1.2.2"}}, + {"name": "glibx arm","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm", "libc": "glibc:2.23"}}, + {"name": "musl arm","filepath": "/some/path", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm", "libc": "musl:1.2.2"}} +] diff --git a/lib-injection/sources/requirements.json b/lib-injection/sources/requirements.json new file mode 100644 index 00000000000..993e89b73c9 --- /dev/null +++ b/lib-injection/sources/requirements.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/DataDog/auto_inject/refs/heads/main/preload_go/cmd/library_requirements_tester/testdata/requirements_schema.json", + "version": 1, + "native_deps": { + "glibc": [{ + "arch": "x64", + "supported": true, + "description": "From manylinux_2_17", + "min": "2.17" + }, + { + "arch": "arm64", + "supported": true, + "description": "From manylinux_2_17", + "min": "2.17" + }], + "musl": [{ + "arch": "x64", + "supported": true, + "description": "From musllinux_1_2 " + },{ + "arch": "arm64", + "supported": true, + "description": "From alpmusllinux_1_2 " + }] + }, + "deny": [] +} \ No newline at end of file diff --git a/releasenotes/notes/add-requirements-json-038073d722697e32.yaml b/releasenotes/notes/add-requirements-json-038073d722697e32.yaml new file mode 100644 index 00000000000..2ffb0cfb76d --- /dev/null +++ b/releasenotes/notes/add-requirements-json-038073d722697e32.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add requirements.json to SSI artifact for bailing out on unsupported systems. \ No newline at end of file From 62727981a6fd702a6fd48f0f35e581aec36ede4f Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:39:25 -0500 Subject: [PATCH 129/372] fix(llmobs): ensure non-ascii characters are not encoded into raw unicode (#11330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a fix to ensure that non-ascii characters (ex: Korean, Chinese, Japanese, etc...) are not encoded to raw unicode values when annotated as input/output values on spans and submitted to LLM Observability. Previously, spans (specifically in the langchain integration's LLM Obs plugin) with Korean input values were JSON dumped directly into the LLM Observability span event and converted into raw unicode (ex: `\uc548\ub155\ud558\uc138\uc694` instead of `안녕하세요`), meaning that LLM Observability out-of-the-box evaluators increased the change of false toxicity flags. With this change to how we JSON dump input/output values (adding the `ensure_ascii=False` option), we now ensure that non-ascii characters are preserved. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_utils.py | 2 +- ...-non-ascii-langchain-eb3263c79ad83ffb.yaml | 4 + .../openai_completion_non_ascii.yaml | 103 ++++++++++++++++++ .../langchain/test_langchain_llmobs.py | 24 ++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-llmobs-non-ascii-langchain-eb3263c79ad83ffb.yaml create mode 100644 tests/contrib/langchain/cassettes/langchain_community/openai_completion_non_ascii.yaml diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 9228662b7f9..f3d6434d297 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -181,6 +181,6 @@ def safe_json(obj): if isinstance(obj, str): return obj try: - return json.dumps(obj, skipkeys=True, default=_unserializable_default_repr) + return json.dumps(obj, ensure_ascii=False, skipkeys=True, default=_unserializable_default_repr) except Exception: log.error("Failed to serialize object to JSON.", exc_info=True) diff --git a/releasenotes/notes/fix-llmobs-non-ascii-langchain-eb3263c79ad83ffb.yaml b/releasenotes/notes/fix-llmobs-non-ascii-langchain-eb3263c79ad83ffb.yaml new file mode 100644 index 00000000000..715027dd806 --- /dev/null +++ b/releasenotes/notes/fix-llmobs-non-ascii-langchain-eb3263c79ad83ffb.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + LLM Observability: Resolves an issue where annotating spans with non-ASCII language input/output values resulted in encoded unicode being submitted. diff --git a/tests/contrib/langchain/cassettes/langchain_community/openai_completion_non_ascii.yaml b/tests/contrib/langchain/cassettes/langchain_community/openai_completion_non_ascii.yaml new file mode 100644 index 00000000000..e0b36254620 --- /dev/null +++ b/tests/contrib/langchain/cassettes/langchain_community/openai_completion_non_ascii.yaml @@ -0,0 +1,103 @@ +interactions: +- request: + body: '{"model": "gpt-3.5-turbo-instruct", "prompt": ["\uc548\ub155,\n \uc9c0\uae08 + \uba87 \uc2dc\uc57c?"], "frequency_penalty": 0, "logit_bias": {}, "max_tokens": + 256, "n": 1, "presence_penalty": 0, "temperature": 0.7, "top_p": 1}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '224' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.30.3 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.30.3 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.5 + method: POST + uri: https://api.openai.com/v1/completions + response: + content: "{\n \"id\": \"cmpl-AR7PCqqSF91C9OXOrXEHEM7WjSBEy\",\n \"object\": + \"text_completion\",\n \"created\": 1731026686,\n \"model\": \"gpt-3.5-turbo-instruct\",\n + \ \"choices\": [\n {\n \"text\": \"\\n\\n\uC88B\uC740 \uC544\uCE68\uC774\uC57C. + \uC9C0\uAE08\uC740 \uC624\uC804 10\uC2DC\uC57C.\",\n \"index\": 0,\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 13,\n \"completion_tokens\": 22,\n \"total_tokens\": 35\n }\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8df189584b2b073e-IAD + Cache-Control: + - no-cache, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 08 Nov 2024 00:44:47 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=SSJPMty2aYNkH30fdWHGZ5HUNGiUmmK3FOv8vTwPeko-1731026687-1.0.1.1-s9t_bwib8TdlaheJ.3l5yPX975WACEndBO1IL_r.IqkDERBstNM5vuAYikOBJliPcrU01otzKiA5KgoPVpKT4w; + path=/; expires=Fri, 08-Nov-24 01:14:47 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=q2U0OlJna6T0cK1ZU2Z3ENJ5pOL6suqB9E0sIaVLPfo-1731026687186-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-model: + - gpt-3.5-turbo-instruct + openai-organization: + - datadog-4 + openai-processing-ms: + - '377' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '3500' + x-ratelimit-limit-tokens: + - '90000' + x-ratelimit-remaining-requests: + - '3499' + x-ratelimit-remaining-tokens: + - '89737' + x-ratelimit-reset-requests: + - 17ms + x-ratelimit-reset-tokens: + - 174ms + x-request-id: + - req_7e3ebdf2479dcd4d3f07fb30b4ba9886 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/contrib/langchain/test_langchain_llmobs.py b/tests/contrib/langchain/test_langchain_llmobs.py index 6e9d4b4f1f8..feba6f73aa5 100644 --- a/tests/contrib/langchain/test_langchain_llmobs.py +++ b/tests/contrib/langchain/test_langchain_llmobs.py @@ -960,6 +960,17 @@ def test_llmobs_streamed_llm( ) ) + def test_llmobs_non_ascii_completion(self, langchain_openai, mock_llmobs_span_writer, mock_tracer): + self._invoke_llm( + llm=langchain_openai.OpenAI(), + prompt="안녕,\n 지금 몇 시야?", + mock_tracer=mock_tracer, + cassette_name="openai_completion_non_ascii.yaml", + ) + assert mock_llmobs_span_writer.enqueue.call_count == 1 + actual_llmobs_span_event = mock_llmobs_span_writer.enqueue.call_args[0][0] + assert actual_llmobs_span_event["meta"]["input"]["messages"][0]["content"] == "안녕,\n 지금 몇 시야?" + @pytest.mark.skipif(LANGCHAIN_VERSION < (0, 1), reason="These tests are for langchain >= 0.1.0") class TestTraceStructureWithLLMIntegrations(SubprocessTestCase): @@ -1113,6 +1124,19 @@ def test_llmobs_with_openai_enabled(self): self._call_openai_llm(OpenAI) self._assert_trace_structure_from_writer_call_args(["workflow", "llm"]) + @run_in_subprocess(env_overrides=openai_env_config) + def test_llmobs_with_openai_enabled_non_ascii_value(self): + """Regression test to ensure that non-ascii text values for workflow spans are not encoded.""" + from langchain_openai import OpenAI + + patch(langchain=True, openai=True) + LLMObs.enable(ml_app="", integrations_enabled=False) + llm = OpenAI() + with get_request_vcr(subdirectory_name="langchain_community").use_cassette("openai_completion_non_ascii.yaml"): + llm.invoke("안녕,\n 지금 몇 시야?") + langchain_span = self.mock_llmobs_span_writer.enqueue.call_args_list[0][0][0] + assert langchain_span["meta"]["input"]["value"] == '[{"content": "안녕,\\n 지금 몇 시야?"}]' + @run_in_subprocess(env_overrides=openai_env_config) def test_llmobs_with_openai_disabled(self): from langchain_openai import OpenAI From 480b01c3a4504ab53c7583767b944644dd064e7a Mon Sep 17 00:00:00 2001 From: wantsui Date: Fri, 8 Nov 2024 15:07:00 -0500 Subject: [PATCH 130/372] chore: remove flaky marker from an aiohttp integration test (#11275) Due to the update in https://github.com/DataDog/dd-trace-py/pull/8465, the current default sampling priority when extracting headers is **USER_KEEP** rather than `None`: https://github.com/DataDog/dd-trace-py/blob/223c261250ef1bd1a7547cd0d6311efc153a2395/ddtrace/propagation/http.py#L316 . The `test_distributed_tracing` test was failing due to this error: ``` FAILED tests/contrib/aiohttp/test_middleware.py::test_distributed_tracing - AssertionError: assert 2 is None ``` That's because the previous test assumed **no sampling priority** would be added to the span but the update changed that. I don't think this should be a flaky test, so I'm removing the marker and adjusting with the current logic. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/aiohttp/test_middleware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/contrib/aiohttp/test_middleware.py b/tests/contrib/aiohttp/test_middleware.py index c054cbdb902..58a6023ac98 100644 --- a/tests/contrib/aiohttp/test_middleware.py +++ b/tests/contrib/aiohttp/test_middleware.py @@ -5,6 +5,7 @@ from ddtrace.constants import ERROR_MSG from ddtrace.constants import SAMPLING_PRIORITY_KEY +from ddtrace.constants import USER_KEEP from ddtrace.contrib.aiohttp.middlewares import CONFIG_KEY from ddtrace.contrib.aiohttp.middlewares import trace_app from ddtrace.contrib.aiohttp.middlewares import trace_middleware @@ -359,7 +360,6 @@ async def test_wrapped_coroutine(app_tracer, aiohttp_client): assert span.duration > 0.25, "span.duration={0}".format(span.duration) -@flaky(1735812000) async def test_distributed_tracing(app_tracer, aiohttp_client): app, tracer = app_tracer client = await aiohttp_client(app) @@ -381,7 +381,7 @@ async def test_distributed_tracing(app_tracer, aiohttp_client): # with the right trace_id and parent_id assert span.trace_id == 100 assert span.parent_id == 42 - assert span.get_metric(SAMPLING_PRIORITY_KEY) is None + assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP @flaky(1735812000) From f9e96e9b4a464d1bb87932e7eaf5c2cb8e02526c Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 11 Nov 2024 13:35:28 -0500 Subject: [PATCH 131/372] chore(ci): remove pinned versions of gevent from profile, opentracer tests (#11344) to unblock us from broken CI ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{1dfcf17.txt => 125c1e6.txt} | 30 +++++----- .../requirements/{1a594ce.txt => 12a4316.txt} | 22 +++---- .../requirements/{1ffb22d.txt => 1413039.txt} | 24 ++++---- .../requirements/{13e7fea.txt => 1452073.txt} | 26 ++++---- .../requirements/{10fe95f.txt => 1560353.txt} | 16 ++--- .riot/requirements/197de71.txt | 31 ---------- .../requirements/{194a34b.txt => 19a43a5.txt} | 28 ++++----- .../requirements/{4132bce.txt => 1edf426.txt} | 22 +++---- .../requirements/{52fe8c7.txt => 1ef2187.txt} | 24 ++++---- .riot/requirements/ac92a08.txt | 32 ---------- .../requirements/{16f0a68.txt => de95112.txt} | 8 +-- .../requirements/{c351d44.txt => eb59b31.txt} | 16 ++--- riotfile.py | 59 +++++-------------- 13 files changed, 122 insertions(+), 216 deletions(-) rename .riot/requirements/{1dfcf17.txt => 125c1e6.txt} (61%) rename .riot/requirements/{1a594ce.txt => 12a4316.txt} (65%) rename .riot/requirements/{1ffb22d.txt => 1413039.txt} (68%) rename .riot/requirements/{13e7fea.txt => 1452073.txt} (68%) rename .riot/requirements/{10fe95f.txt => 1560353.txt} (78%) delete mode 100644 .riot/requirements/197de71.txt rename .riot/requirements/{194a34b.txt => 19a43a5.txt} (61%) rename .riot/requirements/{4132bce.txt => 1edf426.txt} (65%) rename .riot/requirements/{52fe8c7.txt => 1ef2187.txt} (65%) delete mode 100644 .riot/requirements/ac92a08.txt rename .riot/requirements/{16f0a68.txt => de95112.txt} (85%) rename .riot/requirements/{c351d44.txt => eb59b31.txt} (79%) diff --git a/.riot/requirements/1dfcf17.txt b/.riot/requirements/125c1e6.txt similarity index 61% rename from .riot/requirements/1dfcf17.txt rename to .riot/requirements/125c1e6.txt index ad86edc1aee..77714a4a160 100644 --- a/.riot/requirements/1dfcf17.txt +++ b/.riot/requirements/125c1e6.txt @@ -2,32 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dfcf17.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/125c1e6.in # -attrs==23.2.0 -coverage[toml]==7.6.0 +attrs==24.2.0 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 -gevent==21.8.0 -greenlet==1.1.0 -gunicorn[gevent]==22.0.0 +gevent==24.11.1 +greenlet==3.1.1 +gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.1 +pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 +tomli==2.0.2 +uwsgi==2.0.28 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/1a594ce.txt b/.riot/requirements/12a4316.txt similarity index 65% rename from .riot/requirements/1a594ce.txt rename to .riot/requirements/12a4316.txt index 7e3f9aa19be..de9c85604c9 100644 --- a/.riot/requirements/1a594ce.txt +++ b/.riot/requirements/12a4316.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a594ce.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/12a4316.in # -attrs==23.2.0 -coverage[toml]==7.6.0 -gevent==22.8.0 -greenlet==1.1.3.post0 +attrs==24.2.0 +coverage[toml]==7.6.4 +gevent==24.11.1 +greenlet==3.1.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.1 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/1ffb22d.txt b/.riot/requirements/1413039.txt similarity index 68% rename from .riot/requirements/1ffb22d.txt rename to .riot/requirements/1413039.txt index 69a20e3f210..e05e2893ae6 100644 --- a/.riot/requirements/1ffb22d.txt +++ b/.riot/requirements/1413039.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ffb22d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1413039.in # -attrs==23.2.0 -coverage[toml]==7.6.0 +attrs==24.2.0 +coverage[toml]==7.6.1 exceptiongroup==1.2.2 -gevent==20.12.1 -greenlet==1.0.0 +gevent==24.2.1 +greenlet==3.1.1 hypothesis==6.45.0 -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.1 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 +tomli==2.0.2 +zipp==3.20.2 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/13e7fea.txt b/.riot/requirements/1452073.txt similarity index 68% rename from .riot/requirements/13e7fea.txt rename to .riot/requirements/1452073.txt index a9ae6e94a09..e2c59087863 100644 --- a/.riot/requirements/13e7fea.txt +++ b/.riot/requirements/1452073.txt @@ -2,33 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13e7fea.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1452073.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 -gevent==21.8.0 -greenlet==1.1.0 +gevent==24.11.1 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 +tomli==2.0.2 +uwsgi==2.0.28 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.3.0 diff --git a/.riot/requirements/10fe95f.txt b/.riot/requirements/1560353.txt similarity index 78% rename from .riot/requirements/10fe95f.txt rename to .riot/requirements/1560353.txt index 12611af2b08..4b50732d926 100644 --- a/.riot/requirements/10fe95f.txt +++ b/.riot/requirements/1560353.txt @@ -2,30 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/10fe95f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1560353.in # attrs==24.2.0 coverage[toml]==7.6.4 -gevent==23.9.0 +gevent==24.11.1 greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 py-cpuinfo==8.0.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -uwsgi==2.0.27 +uwsgi==2.0.28 zope-event==5.0 zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.2.0 +setuptools==75.3.0 diff --git a/.riot/requirements/197de71.txt b/.riot/requirements/197de71.txt deleted file mode 100644 index 12558ce029e..00000000000 --- a/.riot/requirements/197de71.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/197de71.in -# -attrs==23.2.0 -coverage[toml]==7.6.0 -gevent==22.10.2 -greenlet==3.0.3 -gunicorn[gevent]==22.0.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.1 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -uwsgi==2.0.26 -zope-event==5.0 -zope-interface==6.4.post2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/194a34b.txt b/.riot/requirements/19a43a5.txt similarity index 61% rename from .riot/requirements/194a34b.txt rename to .riot/requirements/19a43a5.txt index b7e8734b55d..25a918eb7ad 100644 --- a/.riot/requirements/194a34b.txt +++ b/.riot/requirements/19a43a5.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/194a34b.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/19a43a5.in # -attrs==23.2.0 -coverage[toml]==7.6.0 +attrs==24.2.0 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 -gevent==21.1.2 -greenlet==1.1.3.post0 +gevent==24.11.1 +greenlet==3.1.1 hypothesis==6.45.0 -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.1 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 +tomli==2.0.2 +zipp==3.21.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/4132bce.txt b/.riot/requirements/1edf426.txt similarity index 65% rename from .riot/requirements/4132bce.txt rename to .riot/requirements/1edf426.txt index 60ecafb5e93..56a5eb28b4d 100644 --- a/.riot/requirements/4132bce.txt +++ b/.riot/requirements/1edf426.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/4132bce.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1edf426.in # -attrs==23.2.0 -coverage[toml]==7.6.0 -gevent==23.9.1 -greenlet==3.0.3 +attrs==24.2.0 +coverage[toml]==7.6.4 +gevent==24.11.1 +greenlet==3.1.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.1 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/52fe8c7.txt b/.riot/requirements/1ef2187.txt similarity index 65% rename from .riot/requirements/52fe8c7.txt rename to .riot/requirements/1ef2187.txt index 844526d9445..b430f5158b2 100644 --- a/.riot/requirements/52fe8c7.txt +++ b/.riot/requirements/1ef2187.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/52fe8c7.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ef2187.in # -attrs==23.2.0 -coverage[toml]==7.6.0 +attrs==24.2.0 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 -gevent==21.8.0 -greenlet==1.1.3.post0 +gevent==24.11.1 +greenlet==3.1.1 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.1 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.0.2 zope-event==5.0 -zope-interface==6.4.post2 +zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 +setuptools==75.3.0 diff --git a/.riot/requirements/ac92a08.txt b/.riot/requirements/ac92a08.txt deleted file mode 100644 index e1d20066e86..00000000000 --- a/.riot/requirements/ac92a08.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ac92a08.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -gevent==22.10.2 -greenlet==3.0.3 -gunicorn[gevent]==23.0.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -lz4==4.3.3 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.2 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -uwsgi==2.0.26 -zope-event==5.0 -zope-interface==7.0.3 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 diff --git a/.riot/requirements/16f0a68.txt b/.riot/requirements/de95112.txt similarity index 85% rename from .riot/requirements/16f0a68.txt rename to .riot/requirements/de95112.txt index 043b35fd3fb..127a4b50d75 100644 --- a/.riot/requirements/16f0a68.txt +++ b/.riot/requirements/de95112.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/16f0a68.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/de95112.in # -attrs==23.2.0 +attrs==24.2.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 -gevent==20.12.1 -greenlet==1.0.0 +gevent==22.10.2 +greenlet==3.1.1 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 diff --git a/.riot/requirements/c351d44.txt b/.riot/requirements/eb59b31.txt similarity index 79% rename from .riot/requirements/c351d44.txt rename to .riot/requirements/eb59b31.txt index 363b32208e7..5899a214883 100644 --- a/.riot/requirements/c351d44.txt +++ b/.riot/requirements/eb59b31.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c351d44.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/eb59b31.in # attrs==24.2.0 coverage[toml]==7.6.4 -gevent==23.9.0 +gevent==24.11.1 greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 @@ -14,19 +14,19 @@ iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 py-cpuinfo==8.0.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -uwsgi==2.0.27 +uwsgi==2.0.28 zope-event==5.0 zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.2.0 +setuptools==75.3.0 diff --git a/riotfile.py b/riotfile.py index 61d0e0b87db..c7fe4b1d72d 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2472,28 +2472,25 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): Venv( pys=select_pys(min_version="3.7", max_version="3.8"), pkgs={ - "gevent": ["~=20.12.0"], - # greenlet>0.4.17 wheels are incompatible with gevent and python>3.7 - # This issue was fixed in gevent v20.9: - # https://github.com/gevent/gevent/issues/1678#issuecomment-697995192 - "greenlet": "~=1.0.0", + "gevent": latest, + "greenlet": latest, }, ), Venv( pys="3.9", - pkgs={"gevent": "~=21.1.0", "greenlet": "~=1.0"}, + pkgs={"gevent": latest, "greenlet": latest}, ), Venv( pys="3.10", - pkgs={"gevent": "~=21.8.0"}, + pkgs={"gevent": latest}, ), Venv( pys="3.11", - pkgs={"gevent": "~=22.8.0"}, + pkgs={"gevent": latest}, ), Venv( pys=select_pys(min_version="3.12"), - pkgs={"gevent": "~=23.9.0"}, + pkgs={"gevent": latest}, ), ], ), @@ -2907,8 +2904,8 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): venvs=[ Venv( pkgs={ - "gevent": "==21.8.0", - "greenlet": "==1.1.0", + "gevent": latest, + "greenlet": latest, } ), Venv( @@ -2933,14 +2930,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): env={ "DD_PROFILE_TEST_GEVENT": "1", }, - pkgs={ - "gunicorn[gevent]": latest, - }, - venvs=[ - Venv( - pkgs={"gevent": ["==22.10.2", latest]}, - ), - ], + pkgs={"gunicorn[gevent]": latest, "gevent": latest}, ), ], ), @@ -2959,14 +2949,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): env={ "DD_PROFILE_TEST_GEVENT": "1", }, - pkgs={ - "gunicorn[gevent]": latest, - }, - venvs=[ - Venv( - pkgs={"gevent": ["==23.9.0"]}, - ), - ], + pkgs={"gunicorn[gevent]": latest, "gevent": latest}, ), ], ), @@ -3054,8 +3037,8 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): venvs=[ Venv( pkgs={ - "gevent": "==21.8.0", - "greenlet": "==1.1.0", + "gevent": latest, + "greenlet": latest, } ), Venv( @@ -3080,14 +3063,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): env={ "DD_PROFILE_TEST_GEVENT": "1", }, - pkgs={ - "gunicorn[gevent]": latest, - }, - venvs=[ - Venv( - pkgs={"gevent": ["==22.10.2", latest]}, - ), - ], + pkgs={"gunicorn[gevent]": latest, "gevent": latest}, ), ], ), @@ -3106,14 +3082,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): env={ "DD_PROFILE_TEST_GEVENT": "1", }, - pkgs={ - "gunicorn[gevent]": latest, - }, - venvs=[ - Venv( - pkgs={"gevent": ["==23.9.0"]}, - ), - ], + pkgs={"gunicorn[gevent]": latest, "gevent": latest}, ), ], ), From 69026ee6533917537fa453b5c2d906843e15ce2d Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 11 Nov 2024 19:25:16 +0000 Subject: [PATCH 132/372] docs: code origin configuration (#11324) We include the code origin configuration to the documentation. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/configuration.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index 45673747b72..3afe7ee817f 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -681,3 +681,9 @@ Exception Replay ---------------- .. ddtrace-envier-configuration:: ddtrace.settings.exception_replay:ExceptionReplayConfig + + +Code Origin +----------- + +.. ddtrace-envier-configuration:: ddtrace.settings.code_origin:CodeOriginConfig From 9de85e35dc95608be7b586739f7d8fe33a7c62bd Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 11 Nov 2024 16:23:02 -0500 Subject: [PATCH 133/372] fix(profiling): clear sample pool after fork (#11350) Tested with native tests `crossbeam::ArrayQueue` uses Rust `std::sync::atomic` which doesn't seem to be consistent across `fork()` calls as `std::mutex` from C++ isn't. So we need to make sure, an equivalent of `std::mutex::unlock()` happens after fork. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/src/sample_manager.cpp | 6 ++++++ .../notes/profiling-sample-pool-461a108e068dea5b.yaml | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 releasenotes/notes/profiling-sample-pool-461a108e068dea5b.yaml diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample_manager.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample_manager.cpp index 310bf3b95bd..ca355cac97c 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample_manager.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample_manager.cpp @@ -74,6 +74,12 @@ void Datadog::SampleManager::postfork_child() { Datadog::Sample::postfork_child(); + if (sample_pool != nullptr) { + // Clear the pool to make sure it's in a consistent state. + // Suppose there was a thread that was adding/removing sample from the pool + // and the fork happened in the middle of that operation. + sample_pool = std::make_unique(sample_pool_capacity); + } } void diff --git a/releasenotes/notes/profiling-sample-pool-461a108e068dea5b.yaml b/releasenotes/notes/profiling-sample-pool-461a108e068dea5b.yaml new file mode 100644 index 00000000000..2038245d266 --- /dev/null +++ b/releasenotes/notes/profiling-sample-pool-461a108e068dea5b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + profiling: fixes an issue where the sample pool could deadlock after ``fork()`` + by clearing it in the child process. + From 498e125e927d6ab5449f6dd9aa0325ba70cfffa1 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:49:23 -0600 Subject: [PATCH 134/372] chore(profiling): bump libdatadog version (#11337) This is a minor version bump for libdatadog. It doesn't really affect anything, except it fixes an issue with the crashtracker altstack creation not being sufficiently granular. This isn't a problem for Python, since we just use and create a sane default. Still, if we ever need this to work properly during an investigation, it should be properly configurable. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Taegyun Kim --- .../datadog/profiling/cmake/FindLibdatadog.cmake | 2 +- .../profiling/cmake/tools/libdatadog_checksums.txt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake index e713722698b..c74851b9e65 100644 --- a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake @@ -5,7 +5,7 @@ endif() include(ExternalProject) set(TAG_LIBDATADOG - "v14.0.0" + "v14.1.0" CACHE STRING "libdatadog github tag") set(Datadog_BUILD_DIR ${CMAKE_BINARY_DIR}/libdatadog) diff --git a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt index d2e19b88f78..a6a65ce0f90 100644 --- a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt +++ b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt @@ -1,5 +1,5 @@ -6aa3a1dd9664f1bb51aa64e647344f48deb0b07a2c0c95cfa40af0fd0463cb08 libdatadog-aarch64-alpine-linux-musl.tar.gz -fa29ac61904b0481bcaaf2cc3aff844ac058ce92d0a4d7cfed25e4f178442359 libdatadog-aarch64-apple-darwin.tar.gz -44cde6f2b406842e9e94b36cc04aadfcc628242c634cf103bde2f4907640d39a libdatadog-aarch64-unknown-linux-gnu.tar.gz -0aaed4bbbd30dc77c9e2cd5c9bbc011d101086eb6eada6332f0a8276cd67b691 libdatadog-x86_64-alpine-linux-musl.tar.gz -c88fa1f191637e7e42776d2139721294cebc697d3cc951b972f677bb08d641fd libdatadog-x86_64-unknown-linux-gnu.tar.gz +fc6be3383d3a115804c43e2c66dd176c63f33b362d987d9b1211034e2b549c2d libdatadog-aarch64-alpine-linux-musl.tar.gz +b9c972afea19696ee6a459d2fa65563b738baf77dcb12739c8e4ae44d1c975fb libdatadog-aarch64-apple-darwin.tar.gz +1a9bc4d99d23f7baf403b6b7527f9b9d76bdb166dc34656150561dcb148cc90b libdatadog-aarch64-unknown-linux-gnu.tar.gz +8244831681332dfa939eefe6923fe6a8beaffff48cb336f836b55a438078add1 libdatadog-x86_64-alpine-linux-musl.tar.gz +76fcb3bfe3b3971d77f6dd4968ffe6bd5f6a1ada82e2e990a78919107dc2ee40 libdatadog-x86_64-unknown-linux-gnu.tar.gz From 7ceea025f75ada875434ebde0501f598a763fd54 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:57:58 -0600 Subject: [PATCH 135/372] chore(profiling): add profiling configuration to crashtracking tags (#11336) This patch adds the profiling configuration to the crashtracking tags. Crashtracking already has tests for this, so it's a simple matter of actually enabling the propagation. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim --- ddtrace/profiling/profiler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index a74136912cc..48789200d7f 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -16,6 +16,7 @@ from ddtrace.internal import service from ddtrace.internal import uwsgi from ddtrace.internal import writer +from ddtrace.internal.core import crashtracking from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.telemetry import telemetry_writer @@ -223,6 +224,7 @@ def _build_default_exporters(self): configured_features.append("CAP" + str(profiling_config.capture_pct)) configured_features.append("MAXF" + str(profiling_config.max_frames)) self.tags.update({"profiler_config": "_".join(configured_features)}) + crashtracking.add_tag("profiler_config", self.tags["profiler_config"]) endpoint_call_counter_span_processor = self.tracer._endpoint_call_counter_span_processor if self.endpoint_collection_enabled: From 1aac18bd67a581a01af5d7894d398c6ea6ce87f9 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:20:43 -0500 Subject: [PATCH 136/372] chore(llmobs): add metadata to the ragas evaluation metric (#11271) Some final UX quirks for ragas faithfulness evaluations. - Add metadata to the faithfulness evaluation metric event. This metadata includes a list of flagged segments from the answer (enables better trace view UX) and the exported span of the faithfulness trace (enables trace link). ``` "metadata": { "_dd": { "evaluation_kind": "faithfulness", "evaluation_span": { "trace_id": "672d238e000000003a5fc64e5f9a9d6f", "span_id": "17204623770592671651" } "disagreements_list": [ {"answer_quote": "..."} ], } }, ``` - Support the ability for users to specify their own custom context & query keys that are used as inputs for faithfulness eval. With these changes, ragas faithfulness will then be aligned with our OOTB hallucination metric. This PR has some small refactors to tests - decouples ragas faithfulness evaluator from the evaluator runner tests (we now only rely on the dummy evaluator in utils) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_constants.py | 4 + .../llmobs/_evaluators/ragas/faithfulness.py | 53 +++- tests/llmobs/_utils.py | 14 + ...s_evaluation_on_span_with_custom_keys.yaml | 279 ++++++++++++++++++ tests/llmobs/test_llmobs_evaluator_runner.py | 33 +-- ...est_llmobs_ragas_faithfulness_evaluator.py | 61 +++- 6 files changed, 402 insertions(+), 42 deletions(-) create mode 100644 tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_custom_keys.yaml diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index 5f33349e938..7c295835e54 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -56,3 +56,7 @@ ANNOTATIONS_CONTEXT_ID = "annotations_context_id" INTERNAL_CONTEXT_VARIABLE_KEYS = "_dd_context_variable_keys" INTERNAL_QUERY_VARIABLE_KEYS = "_dd_query_variable_keys" + +FAITHFULNESS_DISAGREEMENTS_METADATA = "_dd.faithfulness_disagreements" +EVALUATION_KIND_METADATA = "_dd.evaluation_kind" +EVALUATION_SPAN_METADATA = "_dd.evaluation_span" diff --git a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py index 9b0abbd8953..d651c2443a4 100644 --- a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py +++ b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py @@ -3,6 +3,7 @@ import traceback from typing import List from typing import Optional +from typing import Tuple from typing import Union from ddtrace.internal.logger import get_logger @@ -10,6 +11,11 @@ from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.internal.utils.version import parse_version +from ddtrace.llmobs._constants import EVALUATION_KIND_METADATA +from ddtrace.llmobs._constants import EVALUATION_SPAN_METADATA +from ddtrace.llmobs._constants import FAITHFULNESS_DISAGREEMENTS_METADATA +from ddtrace.llmobs._constants import INTERNAL_CONTEXT_VARIABLE_KEYS +from ddtrace.llmobs._constants import INTERNAL_QUERY_VARIABLE_KEYS from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX @@ -163,7 +169,7 @@ def __init__(self, llmobs_service): def run_and_submit_evaluation(self, span_event: dict): if not span_event: return - score_result_or_failure = self.evaluate(span_event) + score_result_or_failure, metric_metadata = self.evaluate(span_event) telemetry_writer.add_count_metric( TELEMETRY_APM_PRODUCT.LLMOBS, "evaluators.run", @@ -179,9 +185,10 @@ def run_and_submit_evaluation(self, span_event: dict): label=RagasFaithfulnessEvaluator.LABEL, metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, value=score_result_or_failure, + metadata=metric_metadata, ) - def evaluate(self, span_event: dict) -> Union[float, str]: + def evaluate(self, span_event: dict) -> Tuple[Union[float, str], Optional[dict]]: """ Performs a faithfulness evaluation on a span event, returning either - faithfulness score (float) OR @@ -191,20 +198,34 @@ def evaluate(self, span_event: dict) -> Union[float, str]: """ self.ragas_faithfulness_instance = _get_faithfulness_instance() if not self.ragas_faithfulness_instance: - return "fail_faithfulness_is_none" - - score, question, answer, context, statements, faithfulness_list = math.nan, None, None, None, None, None + return "fail_faithfulness_is_none", {} + + evaluation_metadata = {EVALUATION_KIND_METADATA: "faithfulness"} # type: dict[str, Union[str, dict, list]] + + # initialize data we annotate for tracing ragas + score, question, answer, context, statements, faithfulness_list = ( + math.nan, + None, + None, + None, + None, + None, + ) with self.llmobs_service.workflow( "dd-ragas.faithfulness", ml_app=_get_ml_app_for_ragas_trace(span_event) ) as ragas_faithfulness_workflow: try: + evaluation_metadata[EVALUATION_SPAN_METADATA] = self.llmobs_service.export_span( + span=ragas_faithfulness_workflow + ) + faithfulness_inputs = self._extract_faithfulness_inputs(span_event) if faithfulness_inputs is None: logger.debug( "Failed to extract question and context from span sampled for ragas_faithfulness evaluation" ) - return "fail_extract_faithfulness_inputs" + return "fail_extract_faithfulness_inputs", evaluation_metadata question = faithfulness_inputs["question"] answer = faithfulness_inputs["answer"] @@ -213,19 +234,23 @@ def evaluate(self, span_event: dict) -> Union[float, str]: statements = self._create_statements(question, answer) if statements is None: logger.debug("Failed to create statements from answer for `ragas_faithfulness` evaluator") - return "statements_is_none" + return "statements_is_none", evaluation_metadata faithfulness_list = self._create_verdicts(context, statements) if faithfulness_list is None: logger.debug("Failed to create faithfulness list `ragas_faithfulness` evaluator") - return "statements_create_faithfulness_list" + return "statements_create_faithfulness_list", evaluation_metadata + + evaluation_metadata[FAITHFULNESS_DISAGREEMENTS_METADATA] = [ + {"answer_quote": answer.statement} for answer in faithfulness_list.__root__ if answer.verdict == 0 + ] score = self._compute_score(faithfulness_list) if math.isnan(score): logger.debug("Score computation returned NaN for `ragas_faithfulness` evaluator") - return "statements_compute_score" + return "statements_compute_score", evaluation_metadata - return score + return score, evaluation_metadata finally: self.llmobs_service.annotate( span=ragas_faithfulness_workflow, @@ -341,10 +366,12 @@ def _extract_faithfulness_inputs(self, span_event: dict) -> Optional[dict]: answer = messages[-1].get("content") if prompt_variables: - question = prompt_variables.get("question") - context = prompt_variables.get("context") + context_keys = prompt.get(INTERNAL_CONTEXT_VARIABLE_KEYS, ["context"]) + question_keys = prompt.get(INTERNAL_QUERY_VARIABLE_KEYS, ["question"]) + context = " ".join([prompt_variables.get(key) for key in context_keys if prompt_variables.get(key)]) + question = " ".join([prompt_variables.get(key) for key in question_keys if prompt_variables.get(key)]) - if not question and len(input_messages) > 0: + if not question and input_messages is not None and len(input_messages) > 0: question = input_messages[-1].get("content") self.llmobs_service.annotate( diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index afafdaf4aab..fb48d15431b 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -12,6 +12,7 @@ from ddtrace._trace.span import Span from ddtrace.ext import SpanTypes from ddtrace.llmobs._utils import _get_span_name +from ddtrace.llmobs._writer import LLMObsEvaluationMetricEvent if vcr: @@ -508,6 +509,19 @@ def run_and_submit_evaluation(self, span): ) +def _dummy_evaluator_eval_metric_event(span_id, trace_id): + return LLMObsEvaluationMetricEvent( + span_id=span_id, + trace_id=trace_id, + score_value=1.0, + ml_app="unnamed-ml-app", + timestamp_ms=mock.ANY, + metric_type="score", + label=DummyEvaluator.LABEL, + tags=["ddtrace.version:{}".format(ddtrace.__version__), "ml_app:unnamed-ml-app"], + ) + + def _expected_ragas_spans(ragas_inputs=None): if not ragas_inputs: ragas_inputs = default_ragas_inputs diff --git a/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_custom_keys.yaml b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_custom_keys.yaml new file mode 100644 index 00000000000..1301513e8aa --- /dev/null +++ b/tests/llmobs/llmobs_cassettes/tests.llmobs.test_llmobs_ragas_faithfulness_evaluator.test_ragas_faithfulness_submits_evaluation_on_span_with_custom_keys.yaml @@ -0,0 +1,279 @@ +interactions: +- request: + body: '{"messages": [{"content": "Given a question, an answer, and sentences from + the answer analyze the complexity of each sentence given under ''sentences'' + and break down each sentence into one or more fully understandable statements + while also ensuring no pronouns are used in each statement. Format the outputs + in JSON.\n\nThe output should be a well-formatted JSON instance that conforms + to the JSON schema below.\n\nAs an example, for the schema {\"properties\": + {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": + \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe + object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. + The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere + is the output JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": + \"#/definitions/Statements\"}, \"definitions\": {\"Statements\": {\"title\": + \"Statements\", \"type\": \"object\", \"properties\": {\"sentence_index\": {\"title\": + \"Sentence Index\", \"description\": \"Index of the sentence from the statement + list\", \"type\": \"integer\"}, \"simpler_statements\": {\"title\": \"Simpler + Statements\", \"description\": \"the simpler statements\", \"type\": \"array\", + \"items\": {\"type\": \"string\"}}}, \"required\": [\"sentence_index\", \"simpler_statements\"]}}}\n```\n\nDo + not return any preamble or explanations, return only a pure JSON string surrounded + by triple backticks (```).\n\nExamples:\n\nquestion: \"Who was Albert Einstein + and what is he best known for?\"\nanswer: \"He was a German-born theoretical + physicist, widely acknowledged to be one of the greatest and most influential + physicists of all time. He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\"\nsentences: \"\\n 0:He was a German-born theoretical physicist, + widely acknowledged to be one of the greatest and most influential physicists + of all time. \\n 1:He was best known for developing the theory of relativity, + he also made important contributions to the development of the theory of quantum + mechanics.\\n \"\nanalysis: ```[{\"sentence_index\": 0, \"simpler_statements\": + [\"Albert Einstein was a German-born theoretical physicist.\", \"Albert Einstein + is recognized as one of the greatest and most influential physicists of all + time.\"]}, {\"sentence_index\": 1, \"simpler_statements\": [\"Albert Einstein + was best known for developing the theory of relativity.\", \"Albert Einstein + also made important contributions to the development of the theory of quantum + mechanics.\"]}]```\n\nYour actual task:\n\nquestion: \"Is france part of europe?\"\nanswer: + \"France is indeed part of europe\"\nsentences: \"\"\nanalysis: \n", "role": + "user"}], "model": "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2915' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4ySS2/bMBCE7/oVxJ6tQFb8qm89pAWCFn2k6MUyJIZayWwkkuWu0Ifh/x5QViQb + bYFedNiPM5pZ8hgJAbqErQB1kKxa18SvP6cPH7/+WGzow/397fe3T1/STfruU/tQvr/7DbOgsI/f + UPGL6kbZ1jXI2pozVh4lY3Cdr2/nSbJerZY9aG2JTZDVjuOFjVttdJwm6SJO1vF8M6gPVisk2Ipd + JIQQx/4bcpoSf8JWJLOXSYtEskbYjoeEAG+bMAFJpImlYZhNUFnDaProRVHsjhkQhonCvLfPen+R + AenQyefEkrFFwxTQLoM3XhqFQpNw0rOwlbjrvHV4k8H+tC+K4vJ3HquOZKhsuqYZ5qcxf2Nr5+0j + DXycV9poOuQeJVkTshJbBz09RULs+z11V9XBeds6ztk+oQmGq2TYE0zXM9F0OUC2LJsL1Qiu/PIS + WeqGLjYNSqoDlpN0uhbZldpegOii9Z9p/uZ9bq5N/T/2E1AKHWOZO4+lVteNp2Mew+v917Fxy31g + oF/E2OaVNjV65/X57VQur5av5Ga+SNcVRKfoGQAA//8DAN2uIFBJAwAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8defb8f19b1c3992-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Nov 2024 19:27:45 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=GMAym2MnyZWZw_ORQOfRH7m0vuREaeNFf6gzJiHGy1k-1731007665-1.0.1.1-nArkb5npme6zrdpZ1L0Fho.0C5Glt5LEHaKERjf0koaMgNHOFfv34RUUXMfeQlNRjorW8a21hX7CcW0rBlX22w; + path=/; expires=Thu, 07-Nov-24 19:57:45 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=2UlWnMHqlwf2iyqQoi_qr_fVNbYS7TQkpnENDD0iUfk-1731007665474-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '458' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999324' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a8016b4905b963ff5fd373af0e60e956 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "Your task is to judge the faithfulness of a + series of statements based on a given context. For each statement you must return + verdict as 1 if the statement can be directly inferred based on the context + or 0 if the statement can not be directly inferred based on the context.\n\nThe + output should be a well-formatted JSON instance that conforms to the JSON schema + below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": + \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": + {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", + \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": + {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output + JSON schema:\n```\n{\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/StatementFaithfulnessAnswer\"}, + \"definitions\": {\"StatementFaithfulnessAnswer\": {\"title\": \"StatementFaithfulnessAnswer\", + \"type\": \"object\", \"properties\": {\"statement\": {\"title\": \"Statement\", + \"description\": \"the original statement, word-by-word\", \"type\": \"string\"}, + \"reason\": {\"title\": \"Reason\", \"description\": \"the reason of the verdict\", + \"type\": \"string\"}, \"verdict\": {\"title\": \"Verdict\", \"description\": + \"the verdict(0/1) of the faithfulness.\", \"type\": \"integer\"}}, \"required\": + [\"statement\", \"reason\", \"verdict\"]}}}\n```\n\nDo not return any preamble + or explanations, return only a pure JSON string surrounded by triple backticks + (```).\n\nExamples:\n\ncontext: \"John is a student at XYZ University. He is + pursuing a degree in Computer Science. He is enrolled in several courses this + semester, including Data Structures, Algorithms, and Database Management. John + is a diligent student and spends a significant amount of time studying and completing + assignments. He often stays late in the library to work on his projects.\"\nstatements: + ```[\"John is majoring in Biology.\", \"John is taking a course on Artificial + Intelligence.\", \"John is a dedicated student.\", \"John has a part-time job.\"]```\nanswer: + ```[{\"statement\": \"John is majoring in Biology.\", \"reason\": \"John''s + major is explicitly mentioned as Computer Science. There is no information suggesting + he is majoring in Biology.\", \"verdict\": 0}, {\"statement\": \"John is taking + a course on Artificial Intelligence.\", \"reason\": \"The context mentions the + courses John is currently enrolled in, and Artificial Intelligence is not mentioned. + Therefore, it cannot be deduced that John is taking a course on AI.\", \"verdict\": + 0}, {\"statement\": \"John is a dedicated student.\", \"reason\": \"The context + states that he spends a significant amount of time studying and completing assignments. + Additionally, it mentions that he often stays late in the library to work on + his projects, which implies dedication.\", \"verdict\": 1}, {\"statement\": + \"John has a part-time job.\", \"reason\": \"There is no information given in + the context about John having a part-time job.\", \"verdict\": 0}]```\n\ncontext: + \"Photosynthesis is a process used by plants, algae, and certain bacteria to + convert light energy into chemical energy.\"\nstatements: ```[\"Albert Einstein + was a genius.\"]```\nanswer: ```[{\"statement\": \"Albert Einstein was a genius.\", + \"reason\": \"The context and statement are unrelated\", \"verdict\": 0}]```\n\nYour + actual task:\n\ncontext: \"hello, france is part of europe\"\nstatements: + \"[\\\"France is part of Europe.\\\"]\"\nanswer: \n", "role": "user"}], "model": + "gpt-4o-mini", "n": 1, "stream": false, "temperature": 1e-08}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3657' + content-type: + - application/json + cookie: + - __cf_bm=GMAym2MnyZWZw_ORQOfRH7m0vuREaeNFf6gzJiHGy1k-1731007665-1.0.1.1-nArkb5npme6zrdpZ1L0Fho.0C5Glt5LEHaKERjf0koaMgNHOFfv34RUUXMfeQlNRjorW8a21hX7CcW0rBlX22w; + _cfuvid=2UlWnMHqlwf2iyqQoi_qr_fVNbYS7TQkpnENDD0iUfk-1731007665474-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.0 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSwW6bQBC98xWjOZsIHBJbvvmQHCJVqprm0MYRrJcBtl12V7tD68jyv1dgbGy1 + VXvh8N68x5s3u48AUJW4ApSNYNk6Ha8/zZ8/rj+wWSc/u8UTPWXt12z9Qtn8y/MLznqF3X4jySfV + jbSt08TKmiMtPQmm3jVd3KZJsri/vxuI1pake1ntOM5s3Cqj4nkyz+JkEafLUd1YJSngCl4jAID9 + 8O1zmpJ2uIJkdkJaCkHUhKvzEAB6q3sERQgqsDCMs4mU1jCZIXpRFK/7DQYWTC0Z3uAKNvjohZEE + KoATnsFW8NB56+hmgzPYoCcRrDmOfm4IBr8dA+2cVlKxfofBMAA3guEfbj/Il0oOf04Pb0VRXEb1 + VHVB9HWZTusRP5x317Z23m7DyJ/xShkVmvwYtN8zsHU4sIcI4G3ouLuqDZ23reOc7XcyveFiOXaM + 02kn9vZuJNmy0BO+TE/ElV9eEgulw8WVUArZUDlJp5OKrlT2gogutv49zZ+8j5srU/+P/URISY6p + zJ2n/iZXG09jnvqX/7exc8tDYAzvganNK2Vq8s6r47urXJ5sRVKm86xKMTpEvwAAAP//AwA6IXqX + hQMAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8defb8f6280b3992-IAD + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Thu, 07 Nov 2024 19:27:46 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - datadog-staging + openai-processing-ms: + - '821' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999152' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_397280c8d63c855801cfdc02b86052b2 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llmobs/test_llmobs_evaluator_runner.py b/tests/llmobs/test_llmobs_evaluator_runner.py index 7f7d685cf0a..7ee7d510276 100644 --- a/tests/llmobs/test_llmobs_evaluator_runner.py +++ b/tests/llmobs/test_llmobs_evaluator_runner.py @@ -5,12 +5,12 @@ import mock import pytest -import ddtrace from ddtrace._trace.span import Span from ddtrace.llmobs._evaluators.runner import EvaluatorRunner from ddtrace.llmobs._evaluators.sampler import EvaluatorRunnerSampler from ddtrace.llmobs._evaluators.sampler import EvaluatorRunnerSamplingRule -from ddtrace.llmobs._writer import LLMObsEvaluationMetricEvent +from tests.llmobs._utils import DummyEvaluator +from tests.llmobs._utils import _dummy_evaluator_eval_metric_event from tests.utils import override_env from tests.utils import override_global_config @@ -18,22 +18,9 @@ DUMMY_SPAN = Span("dummy_span") -def _dummy_ragas_eval_metric_event(span_id, trace_id): - return LLMObsEvaluationMetricEvent( - span_id=span_id, - trace_id=trace_id, - score_value=1.0, - ml_app="unnamed-ml-app", - timestamp_ms=mock.ANY, - metric_type="score", - label="ragas_faithfulness", - tags=["ddtrace.version:{}".format(ddtrace.__version__), "ml_app:unnamed-ml-app"], - ) - - -def test_evaluator_runner_start(mock_evaluator_logs, mock_ragas_evaluator): +def test_evaluator_runner_start(mock_evaluator_logs): evaluator_runner = EvaluatorRunner(interval=0.01, llmobs_service=mock.MagicMock()) - evaluator_runner.evaluators.append(mock_ragas_evaluator) + evaluator_runner.evaluators.append(DummyEvaluator(llmobs_service=mock.MagicMock())) evaluator_runner.start() mock_evaluator_logs.debug.assert_has_calls([mock.call("started %r to %r", "EvaluatorRunner")]) @@ -47,20 +34,20 @@ def test_evaluator_runner_buffer_limit(mock_evaluator_logs): ) -def test_evaluator_runner_periodic_enqueues_eval_metric(LLMObs, mock_llmobs_eval_metric_writer, mock_ragas_evaluator): +def test_evaluator_runner_periodic_enqueues_eval_metric(LLMObs, mock_llmobs_eval_metric_writer): evaluator_runner = EvaluatorRunner(interval=0.01, llmobs_service=LLMObs) - evaluator_runner.evaluators.append(mock_ragas_evaluator(llmobs_service=LLMObs)) + evaluator_runner.evaluators.append(DummyEvaluator(llmobs_service=LLMObs)) evaluator_runner.enqueue({"span_id": "123", "trace_id": "1234"}, DUMMY_SPAN) evaluator_runner.periodic() mock_llmobs_eval_metric_writer.enqueue.assert_called_once_with( - _dummy_ragas_eval_metric_event(span_id="123", trace_id="1234") + _dummy_evaluator_eval_metric_event(span_id="123", trace_id="1234") ) @pytest.mark.vcr_logs -def test_evaluator_runner_timed_enqueues_eval_metric(LLMObs, mock_llmobs_eval_metric_writer, mock_ragas_evaluator): +def test_evaluator_runner_timed_enqueues_eval_metric(LLMObs, mock_llmobs_eval_metric_writer): evaluator_runner = EvaluatorRunner(interval=0.01, llmobs_service=LLMObs) - evaluator_runner.evaluators.append(mock_ragas_evaluator(llmobs_service=LLMObs)) + evaluator_runner.evaluators.append(DummyEvaluator(llmobs_service=LLMObs)) evaluator_runner.start() evaluator_runner.enqueue({"span_id": "123", "trace_id": "1234"}, DUMMY_SPAN) @@ -68,7 +55,7 @@ def test_evaluator_runner_timed_enqueues_eval_metric(LLMObs, mock_llmobs_eval_me time.sleep(0.1) mock_llmobs_eval_metric_writer.enqueue.assert_called_once_with( - _dummy_ragas_eval_metric_event(span_id="123", trace_id="1234") + _dummy_evaluator_eval_metric_event(span_id="123", trace_id="1234") ) diff --git a/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py b/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py index 51da6aed3cf..1f78b538f24 100644 --- a/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py +++ b/tests/llmobs/test_llmobs_ragas_faithfulness_evaluator.py @@ -5,11 +5,10 @@ from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator from ddtrace.span import Span - -from ._utils import _expected_llmobs_llm_span_event -from ._utils import _expected_ragas_spans -from ._utils import _llm_span_with_expected_ragas_inputs_in_messages -from ._utils import _llm_span_with_expected_ragas_inputs_in_prompt +from tests.llmobs._utils import _expected_llmobs_llm_span_event +from tests.llmobs._utils import _expected_ragas_spans +from tests.llmobs._utils import _llm_span_with_expected_ragas_inputs_in_messages +from tests.llmobs._utils import _llm_span_with_expected_ragas_inputs_in_prompt def _llm_span_without_io(): @@ -30,7 +29,8 @@ def test_ragas_faithfulness_throws_if_dependencies_not_present(LLMObs, mock_raga def test_ragas_faithfulness_returns_none_if_inputs_extraction_fails(ragas, mock_llmobs_submit_evaluation, LLMObs): rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) - assert rf_evaluator.evaluate(_llm_span_without_io()) == "fail_extract_faithfulness_inputs" + failure_msg, _ = rf_evaluator.evaluate(_llm_span_without_io()) + assert failure_msg == "fail_extract_faithfulness_inputs" assert rf_evaluator.llmobs_service.submit_evaluation.call_count == 0 @@ -89,6 +89,11 @@ def test_ragas_faithfulness_submits_evaluation(ragas, LLMObs, mock_llmobs_submit label=RagasFaithfulnessEvaluator.LABEL, metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, value=1.0, + metadata={ + "_dd.evaluation_span": {"span_id": mock.ANY, "trace_id": mock.ANY}, + "_dd.faithfulness_disagreements": mock.ANY, + "_dd.evaluation_kind": "faithfulness", + }, ) ] ) @@ -112,6 +117,50 @@ def test_ragas_faithfulness_submits_evaluation_on_span_with_question_in_messages label=RagasFaithfulnessEvaluator.LABEL, metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, value=1.0, + metadata={ + "_dd.evaluation_span": {"span_id": mock.ANY, "trace_id": mock.ANY}, + "_dd.faithfulness_disagreements": mock.ANY, + "_dd.evaluation_kind": "faithfulness", + }, + ) + ] + ) + + +@pytest.mark.vcr_logs +def test_ragas_faithfulness_submits_evaluation_on_span_with_custom_keys(ragas, LLMObs, mock_llmobs_submit_evaluation): + """Test that evaluation is submitted for a valid llm span where the last message content is the question""" + rf_evaluator = RagasFaithfulnessEvaluator(LLMObs) + llm_span = _expected_llmobs_llm_span_event( + Span("dummy"), + prompt={ + "variables": { + "user_input": "Is france part of europe?", + "context_1": "hello, ", + "context_2": "france is ", + "context_3": "part of europe", + }, + "_dd_context_variable_keys": ["context_1", "context_2", "context_3"], + "_dd_query_variable_keys": ["user_input"], + }, + output_messages=[{"content": "France is indeed part of europe"}], + ) + rf_evaluator.run_and_submit_evaluation(llm_span) + rf_evaluator.llmobs_service.submit_evaluation.assert_has_calls( + [ + mock.call( + span_context={ + "span_id": llm_span.get("span_id"), + "trace_id": llm_span.get("trace_id"), + }, + label=RagasFaithfulnessEvaluator.LABEL, + metric_type=RagasFaithfulnessEvaluator.METRIC_TYPE, + value=1.0, + metadata={ + "_dd.evaluation_span": {"span_id": mock.ANY, "trace_id": mock.ANY}, + "_dd.faithfulness_disagreements": mock.ANY, + "_dd.evaluation_kind": "faithfulness", + }, ) ] ) From 28fe2ed7e998175a56986c0e3a63975ec455b916 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:45:35 +0000 Subject: [PATCH 137/372] chore(ci_visibility): fixes for v2 pytest plugin (#11322) This makes a few fixes to tests and the pytest v2 plugin to properly gate around CI Visibility enablement as well as make sure that the service is disabled after tests to prevent state leaks between tests that can cause flakiness based on ordering. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim --- ddtrace/contrib/pytest/_plugin_v2.py | 23 ++++++++++++++----- riotfile.py | 7 ++++++ .../api/test_internal_test_visibility_api.py | 18 +++++++++++++++ tests/ci_visibility/api_client/_util.py | 16 +++++++++++++ tests/ci_visibility/test_ci_visibility.py | 21 +++++++++++++++-- 5 files changed, 77 insertions(+), 8 deletions(-) diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 2f7816343ac..7dc7d278de7 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -11,13 +11,8 @@ from ddtrace.contrib.internal.coverage.patch import run_coverage_report from ddtrace.contrib.internal.coverage.utils import _is_coverage_invoked_by_coverage_run from ddtrace.contrib.internal.coverage.utils import _is_coverage_patched -from ddtrace.contrib.pytest._atr_utils import atr_get_failed_reports -from ddtrace.contrib.pytest._atr_utils import atr_get_teststatus -from ddtrace.contrib.pytest._atr_utils import atr_handle_retries -from ddtrace.contrib.pytest._atr_utils import atr_pytest_terminal_summary_post_yield from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled -from ddtrace.contrib.pytest._retry_utils import get_retry_num from ddtrace.contrib.pytest._types import _pytest_report_teststatus_return_type from ddtrace.contrib.pytest._types import pytest_CallInfo from ddtrace.contrib.pytest._types import pytest_Config @@ -34,6 +29,7 @@ from ddtrace.contrib.pytest._utils import _pytest_marked_to_skip from ddtrace.contrib.pytest._utils import _pytest_version_supports_atr from ddtrace.contrib.pytest._utils import _pytest_version_supports_efd +from ddtrace.contrib.pytest._utils import _pytest_version_supports_retries from ddtrace.contrib.pytest._utils import _TestOutcome from ddtrace.contrib.pytest.constants import FRAMEWORK from ddtrace.contrib.pytest.constants import XFAIL_REASON @@ -62,12 +58,20 @@ from ddtrace.internal.test_visibility.coverage_lines import CoverageLines +if _pytest_version_supports_retries(): + from ddtrace.contrib.pytest._retry_utils import get_retry_num + if _pytest_version_supports_efd(): from ddtrace.contrib.pytest._efd_utils import efd_get_failed_reports from ddtrace.contrib.pytest._efd_utils import efd_get_teststatus from ddtrace.contrib.pytest._efd_utils import efd_handle_retries from ddtrace.contrib.pytest._efd_utils import efd_pytest_terminal_summary_post_yield +if _pytest_version_supports_atr(): + from ddtrace.contrib.pytest._atr_utils import atr_get_failed_reports + from ddtrace.contrib.pytest._atr_utils import atr_get_teststatus + from ddtrace.contrib.pytest._atr_utils import atr_handle_retries + from ddtrace.contrib.pytest._atr_utils import atr_pytest_terminal_summary_post_yield log = get_logger(__name__) @@ -417,7 +421,7 @@ def _process_result(item, call, result) -> _TestOutcome: def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome: pytest_TestReport) -> None: # When ATR or EFD retries are active, we do not want makereport to generate results - if get_retry_num(item.nodeid) is not None: + if _pytest_version_supports_retries() and get_retry_num(item.nodeid) is not None: return original_result = outcome.get_result() @@ -507,6 +511,10 @@ def _pytest_terminal_summary_post_yield(terminalreporter, failed_reports_initial @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_terminal_summary(terminalreporter, exitstatus, config): """Report flaky or failed tests""" + if not is_test_visibility_enabled(): + yield + return + failed_reports_initial_size = None try: failed_reports_initial_size = _pytest_terminal_summary_pre_yield(terminalreporter) @@ -563,6 +571,9 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: def pytest_report_teststatus( report: pytest_TestReport, ) -> _pytest_report_teststatus_return_type: + if not is_test_visibility_enabled(): + return + if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): test_status = atr_get_teststatus(report) if test_status is not None: diff --git a/riotfile.py b/riotfile.py index c7fe4b1d72d..31b91fd889c 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1596,6 +1596,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): }, env={ "DD_AGENT_PORT": "9126", + "_DD_CIVISIBILITY_USE_PYTEST_V2": "1", }, venvs=[ Venv( @@ -1683,6 +1684,9 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "more_itertools": "<8.11.0", "pytest-randomly": latest, }, + env={ + "_DD_CIVISIBILITY_USE_PYTEST_V2": "0", + }, venvs=[ Venv( pys=select_pys(min_version="3.7", max_version="3.9"), @@ -1713,6 +1717,9 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "msgpack": latest, "pytest-randomly": latest, }, + env={ + "_DD_CIVISIBILITY_USE_PYTEST_V2": "0", + }, venvs=[ Venv( venvs=[ diff --git a/tests/ci_visibility/api/test_internal_test_visibility_api.py b/tests/ci_visibility/api/test_internal_test_visibility_api.py index 71679fd19a4..1347a964b31 100644 --- a/tests/ci_visibility/api/test_internal_test_visibility_api.py +++ b/tests/ci_visibility/api/test_internal_test_visibility_api.py @@ -1,3 +1,5 @@ +import pytest + import ddtrace.ext.test_visibility.api as ext_api from ddtrace.internal.ci_visibility import CIVisibility from ddtrace.internal.test_visibility import api @@ -13,6 +15,22 @@ class TestCIITRMixin: Note: these tests do not bother discovering a session as the ITR functionality currently does not rely on sessions. """ + @pytest.fixture(scope="function", autouse=True) + def _disable_ci_visibility(self): + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + yield + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + def test_api_is_item_itr_skippable_test_level(self): with set_up_mock_civisibility( itr_enabled=True, diff --git a/tests/ci_visibility/api_client/_util.py b/tests/ci_visibility/api_client/_util.py index 8d891a6f7f2..8a260fbf3e6 100644 --- a/tests/ci_visibility/api_client/_util.py +++ b/tests/ci_visibility/api_client/_util.py @@ -143,6 +143,22 @@ class TestTestVisibilityAPIClientBase: - good/bad/incorrect API responses """ + @pytest.fixture(scope="function", autouse=True) + def _disable_ci_visibility(self): + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + yield + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + default_git_data = GitData("my_repo_url", "some_branch", "mycommitshaaaaaaalalala") default_configurations = { diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index f4ef545c5bd..b3de47a82b5 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -48,6 +48,23 @@ TEST_SHA_2 = "b3672ea5cbc584124728c48a443825d2940e0eee" +@pytest.fixture(scope="function", autouse=True) +def _disable_ci_visibility(): + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + yield + try: + if CIVisibility.enabled: + CIVisibility.disable() + except Exception: # noqa: E722 + # no-dd-sa:python-best-practices/no-silent-exception + pass + + @contextlib.contextmanager def _dummy_noop_git_client(): with mock.patch.multiple( @@ -611,7 +628,7 @@ def test_civisibilitywriter_coverage_agentless_with_intake_url_param(self): ): _get_connection.return_value.getresponse.return_value.status = 200 dummy_writer._put("", {}, cov_client, no_trace=True) - _get_connection.assert_called_once_with("https://citestcov-intake.datadoghq.com", 2.0) + _get_connection.assert_any_call("https://citestcov-intake.datadoghq.com", 2.0) def test_civisibilitywriter_coverage_evp_proxy_url(self): with _ci_override_env( @@ -631,7 +648,7 @@ def test_civisibilitywriter_coverage_evp_proxy_url(self): with mock.patch("ddtrace.internal.writer.writer.get_connection") as _get_connection: _get_connection.return_value.getresponse.return_value.status = 200 dummy_writer._put("", {}, cov_client, no_trace=True) - _get_connection.assert_called_once_with("http://arandomhost:9126", 2.0) + _get_connection.assert_any_call("http://arandomhost:9126", 2.0) def test_civisibilitywriter_agentless_url_envvar(): From 66f76468b4184cd0f3fd4ad90e783a5fe5378832 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Tue, 12 Nov 2024 09:34:10 -0500 Subject: [PATCH 138/372] chore(profiling): run native tests in ci (#11108) 1. Use `pytest-cpp` to discover and run profiling C++ tests a. `pytest-cpp` will find executables that start with `test_` b. all tests are put into `ddtrace/internal/datadog/profiling/test` and `tests/profiling_v2/native_test` will symlink to it for discovery. 3. `DD_PROFILING_NATIVE_TESTS` flag is used to build native tests a. needed to set this in `build_base_venv` b. setup.py includes native test binaries when the flag is set c. gitlab will also propagate those artifacts ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/package.yml | 2 ++ .../requirements/{acb0de0.txt => 118a763.txt} | 11 +++++---- .../requirements/{1452073.txt => 1359ebb.txt} | 7 +++--- .../requirements/{5286dd4.txt => 15e90ee.txt} | 19 +++++++-------- .../requirements/{1744bab.txt => 1600ae2.txt} | 8 ++++--- .../requirements/{18598d3.txt => 18008a7.txt} | 11 +++++---- .../requirements/{1f484c3.txt => 1a9ec51.txt} | 11 +++++---- .../requirements/{ab034bd.txt => 1b284db.txt} | 19 +++++++-------- .../requirements/{d6c4509.txt => 1bc8c1c.txt} | 13 ++++++----- .../requirements/{d79db4d.txt => 1d20be2.txt} | 15 ++++++------ .../requirements/{eb59b31.txt => 1d21682.txt} | 5 ++-- .../requirements/{1e185ef.txt => 2e36381.txt} | 17 +++++++------- .../requirements/{12526ba.txt => 4dd0ff3.txt} | 13 ++++++----- .../requirements/{7f8c636.txt => 6fe81be.txt} | 6 +++-- .../requirements/{1aecc60.txt => a541d7e.txt} | 9 ++++---- .../requirements/{167dd48.txt => b83f7ca.txt} | 23 ++++++++++--------- .../requirements/{8a0f886.txt => d2b6740.txt} | 6 +++-- .../requirements/{1e17619.txt => d3718d9.txt} | 15 ++++++------ .../requirements/{11aeb6a.txt => d62d369.txt} | 11 +++++---- .../requirements/{18d2618.txt => f59e90e.txt} | 17 +++++++------- .../profiling/dd_wrapper/test/CMakeLists.txt | 17 ++++++++++---- .../dd_wrapper/test/{api.cpp => test_api.cpp} | 0 ...rovenance.cpp => test_code_provenance.cpp} | 0 .../test/{forking.cpp => test_forking.cpp} | 2 +- ...ialization.cpp => test_initialization.cpp} | 0 .../{threading.cpp => test_threading.cpp} | 0 .../profiling/stack_v2/test/CMakeLists.txt | 17 ++++++++++++-- ...n_links.cpp => test_thread_span_links.cpp} | 0 .../datadog/profiling/test/.gitignore | 2 ++ riotfile.py | 8 ++++++- setup.py | 9 +++++++- tests/profiling_v2/native_tests | 1 + 32 files changed, 177 insertions(+), 117 deletions(-) rename .riot/requirements/{acb0de0.txt => 118a763.txt} (79%) rename .riot/requirements/{1452073.txt => 1359ebb.txt} (91%) rename .riot/requirements/{5286dd4.txt => 15e90ee.txt} (77%) rename .riot/requirements/{1744bab.txt => 1600ae2.txt} (85%) rename .riot/requirements/{18598d3.txt => 18008a7.txt} (82%) rename .riot/requirements/{1f484c3.txt => 1a9ec51.txt} (83%) rename .riot/requirements/{ab034bd.txt => 1b284db.txt} (78%) rename .riot/requirements/{d6c4509.txt => 1bc8c1c.txt} (82%) rename .riot/requirements/{d79db4d.txt => 1d20be2.txt} (78%) rename .riot/requirements/{eb59b31.txt => 1d21682.txt} (93%) rename .riot/requirements/{1e185ef.txt => 2e36381.txt} (78%) rename .riot/requirements/{12526ba.txt => 4dd0ff3.txt} (81%) rename .riot/requirements/{7f8c636.txt => 6fe81be.txt} (86%) rename .riot/requirements/{1aecc60.txt => a541d7e.txt} (85%) rename .riot/requirements/{167dd48.txt => b83f7ca.txt} (73%) rename .riot/requirements/{8a0f886.txt => d2b6740.txt} (84%) rename .riot/requirements/{1e17619.txt => d3718d9.txt} (79%) rename .riot/requirements/{11aeb6a.txt => d62d369.txt} (79%) rename .riot/requirements/{18d2618.txt => f59e90e.txt} (75%) rename ddtrace/internal/datadog/profiling/dd_wrapper/test/{api.cpp => test_api.cpp} (100%) rename ddtrace/internal/datadog/profiling/dd_wrapper/test/{code_provenance.cpp => test_code_provenance.cpp} (100%) rename ddtrace/internal/datadog/profiling/dd_wrapper/test/{forking.cpp => test_forking.cpp} (99%) rename ddtrace/internal/datadog/profiling/dd_wrapper/test/{initialization.cpp => test_initialization.cpp} (100%) rename ddtrace/internal/datadog/profiling/dd_wrapper/test/{threading.cpp => test_threading.cpp} (100%) rename ddtrace/internal/datadog/profiling/stack_v2/test/{thread_span_links.cpp => test_thread_span_links.cpp} (100%) create mode 100644 ddtrace/internal/datadog/profiling/test/.gitignore create mode 120000 tests/profiling_v2/native_tests diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 115ada9f264..2f896637599 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -7,6 +7,7 @@ build_base_venvs: variables: CMAKE_BUILD_PARALLEL_LEVEL: 12 PIP_VERBOSE: 1 + DD_PROFILING_NATIVE_TESTS: 1 script: - pip install riot==0.20.0 - riot -P -v generate --python=$PYTHON_VERSION @@ -16,6 +17,7 @@ build_base_venvs: - .riot/venv_* - ddtrace/**/*.so* - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* + - ddtrace/internal/datadog/profiling/test/test_* download_ddtrace_artifacts: image: registry.ddbuild.io/github-cli:v27480869-eafb11d-2.43.0 diff --git a/.riot/requirements/acb0de0.txt b/.riot/requirements/118a763.txt similarity index 79% rename from .riot/requirements/acb0de0.txt rename to .riot/requirements/118a763.txt index 5717bdd07bc..96dddb83cd5 100644 --- a/.riot/requirements/acb0de0.txt +++ b/.riot/requirements/118a763.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/acb0de0.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/118a763.in # attrs==24.2.0 coverage[toml]==7.6.4 @@ -18,9 +18,10 @@ protobuf==5.28.3 py-cpuinfo==8.0.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -uwsgi==2.0.27 +uwsgi==2.0.28 diff --git a/.riot/requirements/1452073.txt b/.riot/requirements/1359ebb.txt similarity index 91% rename from .riot/requirements/1452073.txt rename to .riot/requirements/1359ebb.txt index e2c59087863..75c10c261a8 100644 --- a/.riot/requirements/1452073.txt +++ b/.riot/requirements/1359ebb.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1452073.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1359ebb.in # attrs==24.2.0 coverage[toml]==7.6.4 @@ -22,13 +22,14 @@ pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==5.1.0 pytest-cov==6.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 uwsgi==2.0.28 zope-event==5.0 zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 +setuptools==75.4.0 diff --git a/.riot/requirements/5286dd4.txt b/.riot/requirements/15e90ee.txt similarity index 77% rename from .riot/requirements/5286dd4.txt rename to .riot/requirements/15e90ee.txt index cfa763a35cf..1855f4db88a 100644 --- a/.riot/requirements/5286dd4.txt +++ b/.riot/requirements/15e90ee.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/5286dd4.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/15e90ee.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.0.3 +gevent==24.10.2 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -18,17 +18,18 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 +tomli==2.0.2 +uwsgi==2.0.27 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.2.0 diff --git a/.riot/requirements/1744bab.txt b/.riot/requirements/1600ae2.txt similarity index 85% rename from .riot/requirements/1744bab.txt rename to .riot/requirements/1600ae2.txt index 2b7720a4378..d330d767bc1 100644 --- a/.riot/requirements/1744bab.txt +++ b/.riot/requirements/1600ae2.txt @@ -2,13 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1744bab.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1600ae2.in # attrs==24.2.0 +colorama==0.4.6 coverage[toml]==7.2.7 exceptiongroup==1.2.2 gevent==22.10.2 -greenlet==3.0.3 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 importlib-metadata==6.7.0 @@ -23,12 +24,13 @@ pytest==7.4.4 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==4.1.0 +pytest-cpp==2.5.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 -uwsgi==2.0.26 +uwsgi==2.0.27 zipp==3.15.0 zope-event==5.0 zope-interface==6.4.post2 diff --git a/.riot/requirements/18598d3.txt b/.riot/requirements/18008a7.txt similarity index 82% rename from .riot/requirements/18598d3.txt rename to .riot/requirements/18008a7.txt index 49e2a01fcd8..f27f68fb2ce 100644 --- a/.riot/requirements/18598d3.txt +++ b/.riot/requirements/18008a7.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/18598d3.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18008a7.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 gunicorn==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -14,13 +14,14 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -protobuf==5.28.0 +protobuf==5.28.2 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -uwsgi==2.0.26 +uwsgi==2.0.27 diff --git a/.riot/requirements/1f484c3.txt b/.riot/requirements/1a9ec51.txt similarity index 83% rename from .riot/requirements/1f484c3.txt rename to .riot/requirements/1a9ec51.txt index 8df64afa9f9..832fac60dff 100644 --- a/.riot/requirements/1f484c3.txt +++ b/.riot/requirements/1a9ec51.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f484c3.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a9ec51.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 @@ -17,12 +17,13 @@ packaging==24.1 pluggy==1.5.0 protobuf==3.19.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 +tomli==2.0.2 +uwsgi==2.0.27 diff --git a/.riot/requirements/ab034bd.txt b/.riot/requirements/1b284db.txt similarity index 78% rename from .riot/requirements/ab034bd.txt rename to .riot/requirements/1b284db.txt index 8189def48ce..890031e7402 100644 --- a/.riot/requirements/ab034bd.txt +++ b/.riot/requirements/1b284db.txt @@ -2,16 +2,16 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ab034bd.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b284db.in # attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 gevent==24.2.1 -greenlet==3.0.3 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 @@ -19,18 +19,19 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.2.0 diff --git a/.riot/requirements/d6c4509.txt b/.riot/requirements/1bc8c1c.txt similarity index 82% rename from .riot/requirements/d6c4509.txt rename to .riot/requirements/1bc8c1c.txt index 833f8557797..163028cbf3e 100644 --- a/.riot/requirements/d6c4509.txt +++ b/.riot/requirements/1bc8c1c.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d6c4509.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bc8c1c.in # attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 @@ -18,13 +18,14 @@ packaging==24.1 pluggy==1.5.0 protobuf==3.19.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 diff --git a/.riot/requirements/d79db4d.txt b/.riot/requirements/1d20be2.txt similarity index 78% rename from .riot/requirements/d79db4d.txt rename to .riot/requirements/1d20be2.txt index 53f97535f4c..b3e24418d56 100644 --- a/.riot/requirements/d79db4d.txt +++ b/.riot/requirements/1d20be2.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d79db4d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d20be2.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 @@ -18,13 +18,14 @@ packaging==24.1 pluggy==1.5.0 protobuf==3.19.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 diff --git a/.riot/requirements/eb59b31.txt b/.riot/requirements/1d21682.txt similarity index 93% rename from .riot/requirements/eb59b31.txt rename to .riot/requirements/1d21682.txt index 5899a214883..93fd622b954 100644 --- a/.riot/requirements/eb59b31.txt +++ b/.riot/requirements/1d21682.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/eb59b31.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d21682.in # attrs==24.2.0 coverage[toml]==7.6.4 @@ -21,6 +21,7 @@ pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==5.1.0 pytest-cov==6.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.16.0 sortedcontainers==2.4.0 @@ -29,4 +30,4 @@ zope-event==5.0 zope-interface==7.1.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 +setuptools==75.4.0 diff --git a/.riot/requirements/1e185ef.txt b/.riot/requirements/2e36381.txt similarity index 78% rename from .riot/requirements/1e185ef.txt rename to .riot/requirements/2e36381.txt index e358d5e4586..8629f1a5892 100644 --- a/.riot/requirements/1e185ef.txt +++ b/.riot/requirements/2e36381.txt @@ -2,12 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e185ef.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2e36381.in # attrs==24.2.0 -coverage[toml]==7.6.1 -gevent==24.2.1 -greenlet==3.0.3 +coverage[toml]==7.6.3 +gevent==24.10.2 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -17,16 +17,17 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -uwsgi==2.0.26 +uwsgi==2.0.27 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.2.0 diff --git a/.riot/requirements/12526ba.txt b/.riot/requirements/4dd0ff3.txt similarity index 81% rename from .riot/requirements/12526ba.txt rename to .riot/requirements/4dd0ff3.txt index ecee134c825..539971a6683 100644 --- a/.riot/requirements/12526ba.txt +++ b/.riot/requirements/4dd0ff3.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/12526ba.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4dd0ff3.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 @@ -15,14 +15,15 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -protobuf==5.28.0 +protobuf==5.28.2 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 +tomli==2.0.2 +uwsgi==2.0.27 diff --git a/.riot/requirements/7f8c636.txt b/.riot/requirements/6fe81be.txt similarity index 86% rename from .riot/requirements/7f8c636.txt rename to .riot/requirements/6fe81be.txt index da175e96287..f49865ef3bd 100644 --- a/.riot/requirements/7f8c636.txt +++ b/.riot/requirements/6fe81be.txt @@ -2,9 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/7f8c636.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/6fe81be.in # attrs==24.2.0 +colorama==0.4.6 coverage[toml]==7.2.7 exceptiongroup==1.2.2 gunicorn==23.0.0 @@ -22,13 +23,14 @@ pytest==7.4.4 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==4.1.0 +pytest-cpp==2.5.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 six==1.16.0 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 -uwsgi==2.0.26 +uwsgi==2.0.27 zipp==3.15.0 # The following packages are considered to be unsafe in a requirements file: diff --git a/.riot/requirements/1aecc60.txt b/.riot/requirements/a541d7e.txt similarity index 85% rename from .riot/requirements/1aecc60.txt rename to .riot/requirements/a541d7e.txt index feb58960c23..8b110208b7f 100644 --- a/.riot/requirements/1aecc60.txt +++ b/.riot/requirements/a541d7e.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1aecc60.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a541d7e.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 gunicorn==23.0.0 hypothesis==6.45.0 iniconfig==2.0.0 @@ -16,11 +16,12 @@ packaging==24.1 pluggy==1.5.0 protobuf==4.22.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -uwsgi==2.0.26 +uwsgi==2.0.27 diff --git a/.riot/requirements/167dd48.txt b/.riot/requirements/b83f7ca.txt similarity index 73% rename from .riot/requirements/167dd48.txt rename to .riot/requirements/b83f7ca.txt index e3580c6518a..72d6ac027ea 100644 --- a/.riot/requirements/167dd48.txt +++ b/.riot/requirements/b83f7ca.txt @@ -2,16 +2,16 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/167dd48.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b83f7ca.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.0.3 +gevent==24.10.2 +greenlet==3.1.1 gunicorn[gevent]==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 @@ -19,18 +19,19 @@ opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 zope-event==5.0 -zope-interface==7.0.3 +zope-interface==7.1.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +setuptools==75.2.0 diff --git a/.riot/requirements/8a0f886.txt b/.riot/requirements/d2b6740.txt similarity index 84% rename from .riot/requirements/8a0f886.txt rename to .riot/requirements/d2b6740.txt index 19b8536b36e..83fed7d33d5 100644 --- a/.riot/requirements/8a0f886.txt +++ b/.riot/requirements/d2b6740.txt @@ -2,9 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/8a0f886.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/d2b6740.in # attrs==24.2.0 +colorama==0.4.6 coverage[toml]==7.2.7 exceptiongroup==1.2.2 gunicorn==23.0.0 @@ -22,10 +23,11 @@ pytest==7.4.4 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==4.1.0 +pytest-cpp==2.5.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 -uwsgi==2.0.26 +uwsgi==2.0.27 zipp==3.15.0 diff --git a/.riot/requirements/1e17619.txt b/.riot/requirements/d3718d9.txt similarity index 79% rename from .riot/requirements/1e17619.txt rename to .riot/requirements/d3718d9.txt index 11ba3494e76..83abce2c34c 100644 --- a/.riot/requirements/1e17619.txt +++ b/.riot/requirements/d3718d9.txt @@ -2,29 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e17619.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d3718d9.in # attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -protobuf==5.28.0 +protobuf==5.28.2 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 diff --git a/.riot/requirements/11aeb6a.txt b/.riot/requirements/d62d369.txt similarity index 79% rename from .riot/requirements/11aeb6a.txt rename to .riot/requirements/d62d369.txt index 9107c0564a8..4e6a1334fb6 100644 --- a/.riot/requirements/11aeb6a.txt +++ b/.riot/requirements/d62d369.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/11aeb6a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d62d369.in # attrs==24.2.0 coverage[toml]==7.6.4 @@ -18,9 +18,10 @@ protobuf==4.22.0 py-cpuinfo==8.0.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -uwsgi==2.0.27 +uwsgi==2.0.28 diff --git a/.riot/requirements/18d2618.txt b/.riot/requirements/f59e90e.txt similarity index 75% rename from .riot/requirements/18d2618.txt rename to .riot/requirements/f59e90e.txt index b9b6eae6054..61250075c82 100644 --- a/.riot/requirements/18d2618.txt +++ b/.riot/requirements/f59e90e.txt @@ -2,29 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/18d2618.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f59e90e.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 exceptiongroup==1.2.2 gunicorn==23.0.0 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 lz4==4.3.3 mock==5.1.0 opentracing==2.4.0 packaging==24.1 pluggy==1.5.0 -protobuf==5.28.0 +protobuf==5.28.2 py-cpuinfo==8.0.0 -pytest==8.3.2 +pytest==8.3.3 pytest-asyncio==0.21.1 pytest-benchmark==4.0.0 pytest-cov==5.0.0 +pytest-cpp==2.6.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -uwsgi==2.0.26 -zipp==3.20.1 +tomli==2.0.2 +uwsgi==2.0.27 +zipp==3.20.2 diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index 9ff45b93108..e7fe2ceb3ed 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -22,11 +22,18 @@ function(dd_wrapper_add_test name) add_ddup_config(${name}) gtest_discover_tests(${name}) + + set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/..") + + if(LIB_INSTALL_DIR) + install(TARGETS ${name} + RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) + endif() endfunction() # Add the tests -dd_wrapper_add_test(initialization initialization.cpp) -dd_wrapper_add_test(api api.cpp) -dd_wrapper_add_test(threading threading.cpp) -dd_wrapper_add_test(forking forking.cpp) -dd_wrapper_add_test(code_provenance code_provenance.cpp) +dd_wrapper_add_test(test_initialization test_initialization.cpp) +dd_wrapper_add_test(test_api test_api.cpp) +dd_wrapper_add_test(test_threading test_threading.cpp) +dd_wrapper_add_test(test_forking test_forking.cpp) +dd_wrapper_add_test(test_code_provenance test_code_provenance.cpp) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/api.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp similarity index 100% rename from ddtrace/internal/datadog/profiling/dd_wrapper/test/api.cpp rename to ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/code_provenance.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_code_provenance.cpp similarity index 100% rename from ddtrace/internal/datadog/profiling/dd_wrapper/test/code_provenance.cpp rename to ddtrace/internal/datadog/profiling/dd_wrapper/test/test_code_provenance.cpp diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/forking.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp similarity index 99% rename from ddtrace/internal/datadog/profiling/dd_wrapper/test/forking.cpp rename to ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp index e7af54abc10..e02849248e6 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/forking.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp @@ -64,7 +64,7 @@ sample_in_threads_and_fork(unsigned int num_threads, unsigned int sleep_time_ns) int status; done.store(true); waitpid(pid, &status, 0); - upload_in_thread(); + ddup_upload(); if (!is_exit_normal(status)) { std::exit(1); } diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/initialization.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp similarity index 100% rename from ddtrace/internal/datadog/profiling/dd_wrapper/test/initialization.cpp rename to ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/threading.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp similarity index 100% rename from ddtrace/internal/datadog/profiling/dd_wrapper/test/threading.cpp rename to ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt index 23fcda3eedb..dd8e149f54c 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -15,11 +15,24 @@ include(AnalysisFunc) function(dd_wrapper_add_test name) add_executable(${name} ${ARGN}) target_include_directories(${name} PRIVATE ../include) - target_link_libraries(${name} PRIVATE gmock gtest_main _stack_v2) + # this has to refer to the stack_v2 extension name to properly link against + target_link_libraries(${name} PRIVATE gmock gtest_main ${EXTENSION_NAME}) + set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/../stack_v2") + add_ddup_config(${name}) gtest_discover_tests(${name}) + + # This is supplemental artifact so make sure to install it in the right place + if(INPLACE_LIB_INSTALL_DIR) + set(LIB_INSTALL_DIR "${INPLACE_LIB_INSTALL_DIR}") + endif() + + if(LIB_INSTALL_DIR) + install(TARGETS ${name} + RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) + endif() endfunction() # Add the tests -dd_wrapper_add_test(thread_span_links thread_span_links.cpp) +dd_wrapper_add_test(test_thread_span_links test_thread_span_links.cpp) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/test/test_thread_span_links.cpp similarity index 100% rename from ddtrace/internal/datadog/profiling/stack_v2/test/thread_span_links.cpp rename to ddtrace/internal/datadog/profiling/stack_v2/test/test_thread_span_links.cpp diff --git a/ddtrace/internal/datadog/profiling/test/.gitignore b/ddtrace/internal/datadog/profiling/test/.gitignore new file mode 100644 index 00000000000..d6b7ef32c84 --- /dev/null +++ b/ddtrace/internal/datadog/profiling/test/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/riotfile.py b/riotfile.py index 31b91fd889c..c31945aedf3 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2966,10 +2966,16 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): name="profile-v2", # NB riot commands that use this Venv must include --pass-env to work properly command="python -m tests.profiling.run pytest -v --no-cov --capture=no --benchmark-disable {cmdargs} tests/profiling_v2", # noqa: E501 - env={"DD_PROFILING_ENABLE_ASSERTS": "1", "DD_PROFILING_EXPORT_LIBDD_ENABLED": "1"}, + env={ + "DD_PROFILING_ENABLE_ASSERTS": "1", + "DD_PROFILING_EXPORT_LIBDD_ENABLED": "1", + # Enable pytest v2 plugin to handle pytest-cpp items in the test suite + "_DD_CIVISIBILITY_USE_PYTEST_V2": "1", + }, pkgs={ "gunicorn": latest, "lz4": latest, + "pytest-cpp": latest, # # pytest-benchmark depends on cpuinfo which dropped support for Python<=3.6 in 9.0 # See https://github.com/workhorsy/py-cpuinfo/issues/177 diff --git a/setup.py b/setup.py index 727de14e037..ce1aa685596 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,8 @@ CRASHTRACKER_DIR = HERE / "ddtrace" / "internal" / "datadog" / "profiling" / "crashtracker" STACK_V2_DIR = HERE / "ddtrace" / "internal" / "datadog" / "profiling" / "stack_v2" +BUILD_PROFILING_NATIVE_TESTS = os.getenv("DD_PROFILING_NATIVE_TESTS", "0").lower() in ("1", "yes", "on", "true") + CURRENT_OS = platform.system() LIBDDWAF_VERSION = "1.20.1" @@ -349,6 +351,9 @@ def build_extension_cmake(self, ext): "-DEXTENSION_NAME={}".format(extension_basename), ] + if BUILD_PROFILING_NATIVE_TESTS: + cmake_args += ["-DBUILD_TESTING=ON"] + # If it's been enabled, also propagate sccache to the CMake build. We have to manually set the default CC/CXX # compilers here, because otherwise the way we wrap sccache will conflict with the CMake wrappers sccache_path = os.getenv("DD_SCCACHE_PATH") @@ -567,7 +572,9 @@ def get_exts_for(name): "ddtrace.appsec": ["rules.json"], "ddtrace.appsec._ddwaf": ["libddwaf/*/lib/libddwaf.*"], "ddtrace.appsec._iast._taint_tracking": ["CMakeLists.txt"], - "ddtrace.internal.datadog.profiling": ["libdd_wrapper*.*"], + "ddtrace.internal.datadog.profiling": ( + ["libdd_wrapper*.*"] + ["ddtrace/internal/datadog/profiling/test/*"] if BUILD_PROFILING_NATIVE_TESTS else [] + ), "ddtrace.internal.datadog.profiling.crashtracker": ["crashtracker_exe*"], }, zip_safe=False, diff --git a/tests/profiling_v2/native_tests b/tests/profiling_v2/native_tests new file mode 120000 index 00000000000..9173c0212ba --- /dev/null +++ b/tests/profiling_v2/native_tests @@ -0,0 +1 @@ +../../ddtrace/internal/datadog/profiling/test \ No newline at end of file From 9e7acd09ba1d39f92761fc52e68677390b2d93c6 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Tue, 12 Nov 2024 11:59:57 -0500 Subject: [PATCH 139/372] chore: add missing server address tags to snapshot tests (#11349) Continuation of #10064 to add `server.address` tags to DB clients to comply with OTel standards. Update `django` snapshot files, and modify the logic to set `server_address` using the existing internal method (for future instrumentations). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/trace_utils.py | 4 ++++ ...ngo.test_django_snapshots.test_psycopg2_query_default.json | 1 + ...ngo.test_django_snapshots.test_psycopg3_query_default.json | 1 + 3 files changed, 6 insertions(+) diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py index 00ea2a4382d..b6093e71af0 100644 --- a/ddtrace/contrib/trace_utils.py +++ b/ddtrace/contrib/trace_utils.py @@ -431,6 +431,7 @@ def set_http_meta( method=None, # type: Optional[str] url=None, # type: Optional[str] target_host=None, # type: Optional[str] + server_address=None, # type: Optional[str] status_code=None, # type: Optional[Union[int, str]] status_msg=None, # type: Optional[str] query=None, # type: Optional[str] @@ -474,6 +475,9 @@ def set_http_meta( if target_host is not None: span.set_tag_str(net.TARGET_HOST, target_host) + if server_address is not None: + span.set_tag_str(net.SERVER_ADDRESS, server_address) + if status_code is not None: try: int_status_code = int(status_code) diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json index 9e6545e4228..de9cdf41eb5 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json @@ -48,6 +48,7 @@ "db.system": "postgresql", "db.user": "postgres", "out.host": "127.0.0.1", + "server.address": "127.0.0.1", "span.kind": "client" }, "metrics": { diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json index 6d4d06cfc64..c0d50873757 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json @@ -48,6 +48,7 @@ "db.system": "postgresql", "db.user": "postgres", "out.host": "127.0.0.1", + "server.address": "127.0.0.1", "span.kind": "client" }, "metrics": { From 20b44758c29b283251846d5219cbc5597f4bc0e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:26:11 -0500 Subject: [PATCH 140/372] chore: update pymongo latest version to 4.10.1 (#11342) Update pymongo lockfiles and dependency package lockfiles that use `latest`. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Taegyun Kim --- .riot/requirements/12616cb.txt | 12 ++++++------ .riot/requirements/13015fd.txt | 6 +++--- .riot/requirements/13fe884.txt | 8 ++++---- .riot/requirements/167d6de.txt | 4 ++-- .riot/requirements/185fc1c.txt | 4 ++-- .riot/requirements/19aba18.txt | 6 +++--- .riot/requirements/1dbb110.txt | 16 ++++++++-------- .riot/requirements/1e60db0.txt | 8 ++++---- .riot/requirements/1eb9abd.txt | 14 +++++++------- .riot/requirements/1fd0884.txt | 12 ++++++------ .riot/requirements/5a48bdf.txt | 14 +++++++------- .riot/requirements/6cb445e.txt | 16 ++++++++-------- .riot/requirements/8f46789.txt | 12 ++++++------ .riot/requirements/a0454b7.txt | 12 ++++++------ .riot/requirements/a98b986.txt | 12 ++++++------ .riot/requirements/b1f6b59.txt | 10 +++++----- .riot/requirements/d0fc014.txt | 12 ++++++------ .riot/requirements/d66afaf.txt | 4 ++-- .riot/requirements/de7d3ce.txt | 12 ++++++------ 19 files changed, 97 insertions(+), 97 deletions(-) diff --git a/.riot/requirements/12616cb.txt b/.riot/requirements/12616cb.txt index 31f15cfe47c..3c82298cb9b 100644 --- a/.riot/requirements/12616cb.txt +++ b/.riot/requirements/12616cb.txt @@ -5,7 +5,7 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/12616cb.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -13,13 +13,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.9.0 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/13015fd.txt b/.riot/requirements/13015fd.txt index 91e0fd2f268..8c2770377f7 100644 --- a/.riot/requirements/13015fd.txt +++ b/.riot/requirements/13015fd.txt @@ -14,13 +14,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/13fe884.txt b/.riot/requirements/13fe884.txt index 01112f7f592..30a7765ba28 100644 --- a/.riot/requirements/13fe884.txt +++ b/.riot/requirements/13fe884.txt @@ -5,17 +5,17 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/13fe884.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.12.3 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/167d6de.txt b/.riot/requirements/167d6de.txt index db7cabb99b9..1ee4a67ee64 100644 --- a/.riot/requirements/167d6de.txt +++ b/.riot/requirements/167d6de.txt @@ -13,7 +13,7 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.8.0 pytest==8.3.3 @@ -21,5 +21,5 @@ pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/185fc1c.txt b/.riot/requirements/185fc1c.txt index 7878cf85378..8f051b3b5aa 100644 --- a/.riot/requirements/185fc1c.txt +++ b/.riot/requirements/185fc1c.txt @@ -13,7 +13,7 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.13.0 pytest==8.3.3 @@ -21,5 +21,5 @@ pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/19aba18.txt b/.riot/requirements/19aba18.txt index 36dde507408..33c5569110a 100644 --- a/.riot/requirements/19aba18.txt +++ b/.riot/requirements/19aba18.txt @@ -14,13 +14,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/1dbb110.txt b/.riot/requirements/1dbb110.txt index 0c1bd066ca7..1e6155c399f 100644 --- a/.riot/requirements/1dbb110.txt +++ b/.riot/requirements/1dbb110.txt @@ -5,8 +5,8 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1dbb110.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -14,13 +14,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/1e60db0.txt b/.riot/requirements/1e60db0.txt index d6296d91205..87566f6065c 100644 --- a/.riot/requirements/1e60db0.txt +++ b/.riot/requirements/1e60db0.txt @@ -5,17 +5,17 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1e60db0.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.12.3 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1eb9abd.txt b/.riot/requirements/1eb9abd.txt index 68c39ed9da2..f7ad0322c2f 100644 --- a/.riot/requirements/1eb9abd.txt +++ b/.riot/requirements/1eb9abd.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1eb9abd.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 diff --git a/.riot/requirements/1fd0884.txt b/.riot/requirements/1fd0884.txt index e938b48c6a7..d529a0f1ec7 100644 --- a/.riot/requirements/1fd0884.txt +++ b/.riot/requirements/1fd0884.txt @@ -5,7 +5,7 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1fd0884.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -13,13 +13,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.8.0 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/5a48bdf.txt b/.riot/requirements/5a48bdf.txt index 766ff8d17fd..0bcb9027eee 100644 --- a/.riot/requirements/5a48bdf.txt +++ b/.riot/requirements/5a48bdf.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/5a48bdf.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 diff --git a/.riot/requirements/6cb445e.txt b/.riot/requirements/6cb445e.txt index a5d1b34beda..743b7ed0241 100644 --- a/.riot/requirements/6cb445e.txt +++ b/.riot/requirements/6cb445e.txt @@ -5,8 +5,8 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/6cb445e.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -14,13 +14,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/8f46789.txt b/.riot/requirements/8f46789.txt index f2fb80a6bee..6284e327733 100644 --- a/.riot/requirements/8f46789.txt +++ b/.riot/requirements/8f46789.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/8f46789.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/a0454b7.txt b/.riot/requirements/a0454b7.txt index 16e7d2d1138..9713d010afb 100644 --- a/.riot/requirements/a0454b7.txt +++ b/.riot/requirements/a0454b7.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/a0454b7.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/a98b986.txt b/.riot/requirements/a98b986.txt index e82b9df5f0a..f3aa18dbe71 100644 --- a/.riot/requirements/a98b986.txt +++ b/.riot/requirements/a98b986.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/a98b986.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/b1f6b59.txt b/.riot/requirements/b1f6b59.txt index c5ef539715f..bd4ef3538c7 100644 --- a/.riot/requirements/b1f6b59.txt +++ b/.riot/requirements/b1f6b59.txt @@ -5,19 +5,19 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/b1f6b59.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.12.3 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 diff --git a/.riot/requirements/d0fc014.txt b/.riot/requirements/d0fc014.txt index 90ab9109e43..9c4444b6b15 100644 --- a/.riot/requirements/d0fc014.txt +++ b/.riot/requirements/d0fc014.txt @@ -5,7 +5,7 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/d0fc014.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.4 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 @@ -13,13 +13,13 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.13.0 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/d66afaf.txt b/.riot/requirements/d66afaf.txt index 2646a4200b9..8468d6fd083 100644 --- a/.riot/requirements/d66afaf.txt +++ b/.riot/requirements/d66afaf.txt @@ -13,7 +13,7 @@ iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pymongo==3.9.0 pytest==8.3.3 @@ -21,5 +21,5 @@ pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/de7d3ce.txt b/.riot/requirements/de7d3ce.txt index c4a7640ed31..d74fc75f0c9 100644 --- a/.riot/requirements/de7d3ce.txt +++ b/.riot/requirements/de7d3ce.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/de7d3ce.in # attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 +coverage[toml]==7.6.4 +dnspython==2.7.0 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 mongoengine==0.29.1 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pymongo==4.10.0 +pymongo==4.10.1 pytest==8.3.3 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 From 35ec4322c86372c4ab725f35e36e79ead41ad948 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 12 Nov 2024 20:45:28 +0000 Subject: [PATCH 141/372] ci(profiling): v1/v2 matrix in Django overhead job (#11361) We run the Django overhead profile job with the profiler enabled and profile both stack v1 and v2. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/django-overhead-profile.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index 3d5add5d559..55f023f0c8f 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -13,12 +13,19 @@ on: jobs: django-overhead-profile: runs-on: ubuntu-latest + strategy: + matrix: + include: + - suffix: "-v1" + stack_v2: "0" + - suffix: "-v2" + stack_v2: "1" env: PREFIX: ${{ github.workspace }}/prefix DD_CODE_ORIGIN_FOR_SPANS_ENABLED: "1" DD_PROFILING_ENABLED: "1" - DD_PROFILING_STACK_V2_ENABLED: "1" - DD_PROFILING_OUTPUT_PPROF: ${{ github.workspace }}/prefix/artifacts/ddtrace_profile.pprof + DD_PROFILING_STACK_V2_ENABLED: ${{ matrix.stack_v2 }} + DD_PROFILING_OUTPUT_PPROF: ${{ github.workspace }}/prefix/artifacts/ddtrace_profile defaults: run: working-directory: ddtrace @@ -41,6 +48,6 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: django-overhead-profile + name: django-overhead-profile${{ matrix.suffix }} path: ${{ github.workspace }}/prefix/artifacts From c32f043f05fe6569e6aaae4f64f0668d90eb3c74 Mon Sep 17 00:00:00 2001 From: Steven Bouwkamp Date: Tue, 12 Nov 2024 16:35:51 -0500 Subject: [PATCH 142/372] feat(tracer): implement AWS payload tagging for request/response (#10642) ## Overview This pull request adds the ability to expand AWS request/response payloads as span tags. This matches our lambda offerings and provides useful information to developers when debugging communication between various AWS services. This is based on the AWS Payload Tagging RFC and this implementation in [dd-trace-node](https://github.com/DataDog/dd-trace-js/pull/4309) and this implementation in [dd-trace-java](https://github.com/DataDog/dd-trace-java/pull/7312). This feature is _disabled_ by default. When activated this will produce span tags such as: ``` "aws.request.body.PublishBatchRequestEntries.0.Id": "1", "aws.request.body.PublishBatchRequestEntries.0.Message": "ironmaiden", "aws.request.body.PublishBatchRequestEntries.1.Id": "2", "aws.request.body.PublishBatchRequestEntries.1.Message": "megadeth" "aws.response.body.HTTPStatusCode": "200", ``` ## Configuration There are five new configuration options: - `DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING`: - `""` by default to indicate that AWS request payload expansion is **disabled** for _requests_. - `"all"` to define that AWS request payload expansion is **enabled** for _requests_ using the default `JSONPath`s for redaction logic. - a comma-separated list of user-supplied `JSONPath`s to define that AWS request payload expansion is **enabled** for _requests_ using the default `JSONPath`s and the user-supplied `JSONPath`s for redaction logic. - `DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING`: - `""` by default to indicate that AWS response payload expansion is **disabled** for _responses_. - `"all"` to define that AWS response payload expansion is **enabled** for _responses_ using the default `JSONPath`s for redaction logic. - a comma-separated list of user-supplied `JSONPath`s to define that AWS request payload expansion is **enabled** for _responses_ using the default `JSONPath`s and the user-supplied `JSONPath`s for redaction logic. - `DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH` (not defined in RFC but done to match NodeJS): - sets the depth after which we stop creating tags from a payload - defaults to a value of `10` - `DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_TAGS` (to match Java implementation) - sets the maximum number of tags allowed to be expanded - defaults to a value of `758` - `DD_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES` (to match Java implementation) - a comma-separated list of supported AWS services - defaults to ` s3,sns,sqs,kinesis,eventbridge` ## Other - [`jsonpath-ng` has been vendored](https://github.com/h2non/jsonpath-ng/blob/master/jsonpath_ng/jsonpath.py) - [`ply` has been vendored (v3.11) (dependency of `jsonpath-ng`)](https://github.com/dabeaz/ply/releases/tag/3.11) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- .../utils_botocore/aws_payload_tagging.py | 242 ++ ddtrace/_trace/utils_botocore/span_tags.py | 14 + ddtrace/contrib/internal/botocore/patch.py | 11 + ddtrace/vendor/__init__.py | 27 + ddtrace/vendor/jsonpath_ng/__init__.py | 6 + ddtrace/vendor/jsonpath_ng/exceptions.py | 10 + ddtrace/vendor/jsonpath_ng/jsonpath.py | 815 ++++ ddtrace/vendor/jsonpath_ng/lexer.py | 171 + ddtrace/vendor/jsonpath_ng/parser.py | 198 + ddtrace/vendor/ply/__init__.py | 5 + ddtrace/vendor/ply/lex.py | 1098 ++++++ ddtrace/vendor/ply/yacc.py | 3502 +++++++++++++++++ docs/configuration.rst | 39 + docs/spelling_wordlist.txt | 4 + ...-aws-payload-tagging-d01f0033c7e1f5c0.yaml | 5 + .../test_flask_entrypoint_iast_patches.py | 2 +- tests/conftest.py | 11 +- tests/contrib/botocore/test.py | 288 ++ ....test_aws_payload_tagging_eventbridge.json | 358 ++ ...Test.test_aws_payload_tagging_kinesis.json | 176 + ...ocoreTest.test_aws_payload_tagging_s3.json | 196 + ...aws_payload_tagging_s3_invalid_config.json | 196 + ...t_aws_payload_tagging_s3_valid_config.json | 196 + ...coreTest.test_aws_payload_tagging_sns.json | 386 ++ ..._aws_payload_tagging_sns_valid_config.json | 386 ++ ...coreTest.test_aws_payload_tagging_sqs.json | 205 + 26 files changed, 8544 insertions(+), 3 deletions(-) create mode 100644 ddtrace/_trace/utils_botocore/aws_payload_tagging.py create mode 100644 ddtrace/vendor/jsonpath_ng/__init__.py create mode 100644 ddtrace/vendor/jsonpath_ng/exceptions.py create mode 100644 ddtrace/vendor/jsonpath_ng/jsonpath.py create mode 100644 ddtrace/vendor/jsonpath_ng/lexer.py create mode 100644 ddtrace/vendor/jsonpath_ng/parser.py create mode 100644 ddtrace/vendor/ply/__init__.py create mode 100644 ddtrace/vendor/ply/lex.py create mode 100644 ddtrace/vendor/ply/yacc.py create mode 100644 releasenotes/notes/add-aws-payload-tagging-d01f0033c7e1f5c0.yaml create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json create mode 100644 tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json diff --git a/ddtrace/_trace/utils_botocore/aws_payload_tagging.py b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py new file mode 100644 index 00000000000..dadb6749a12 --- /dev/null +++ b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py @@ -0,0 +1,242 @@ +import copy +from decimal import Decimal +import json +from typing import Any +from typing import Dict +from typing import Optional + +from ddtrace import Span +from ddtrace import config +from ddtrace.vendor.jsonpath_ng import parse + + +_MAX_TAG_VALUE_LENGTH = 5000 + + +class AWSPayloadTagging: + _INCOMPLETE_TAG = "_dd.payload_tags_incomplete" # Set to True if MAX_TAGS is reached + + _REDACTION_PATHS_DEFAULTS = [ + # SNS + "$..Attributes.KmsMasterKeyId", + "$..Attributes.Token", + # EventBridge + "$..AuthParameters.OAuthParameters.OAuthHttpParameters.HeaderParameters[*].Value", + "$..AuthParameters.OAuthParameters.OAuthHttpParameters.QueryStringParameters[*].Value", + "$..AuthParameters.OAuthParameters.OAuthHttpParameters.BodyParameters[*].Value", + "$..AuthParameters.InvocationHttpParameters.HeaderParameters[*].Value", + "$..AuthParameters.InvocationHttpParameters.QueryStringParameters[*].Value", + "$..AuthParameters.InvocationHttpParameters.BodyParameters[*].Value", + "$..Targets[*].RedshiftDataParameters.Sql", + "$..Targets[*].RedshiftDataParameters.Sqls", + "$..Targets[*].AppSyncParameters.GraphQLOperation", + # // S3 + "$..SSEKMSKeyId", + "$..SSEKMSEncryptionContext", + ] + _REQUEST_REDACTION_PATHS_DEFAULTS = [ + # Sns + "$..Attributes.PlatformCredential", + "$..Attributes.PlatformPrincipal", + "$..AWSAccountId", + "$..Endpoint", + "$..Token", + "$..OneTimePassword", + "$..phoneNumber", + "$..PhoneNumber", + # EventBridge + "$..AuthParameters.BasicAuthParameters.Password", + "$..AuthParameters.OAuthParameters.ClientParameters.ClientSecret", + "$..AuthParameters.ApiKeyAuthParameters.ApiKeyValue", + # S3 + "$..SSECustomerKey", + "$..CopySourceSSECustomerKey", + "$..RestoreRequest.OutputLocation.S3.Encryption.KMSKeyId", + ] + + _RESPONSE_REDACTION_PATHS_DEFAULTS = [ + # // Sns + "$..Endpoints.*.Token", + "$..PlatformApplication.*.PlatformCredential", + "$..PlatformApplication.*.PlatformPrincipal", + "$..Subscriptions.*.Endpoint", + "$..PhoneNumbers[*].PhoneNumber", + "$..phoneNumbers[*]", + # // S3 + "$..Credentials.SecretAccessKey", + "$..Credentials.SessionToken", + ] + + def __init__(self): + self.current_tag_count = 0 + self.validated = False + self.request_redaction_paths = None + self.response_redaction_paths = None + + def expand_payload_as_tags(self, span: Span, result: Dict[str, Any], key: str) -> None: + """ + Expands the JSON payload from various AWS services into tags and sets them on the Span. + """ + if not self.validated: + self.request_redaction_paths = self._get_redaction_paths_request() + self.response_redaction_paths = self._get_redaction_paths_response() + self.validated = True + + if not self.request_redaction_paths and not self.response_redaction_paths: + return + + if not result: + return + + # we will be redacting at least one of request/response + redacted_dict = copy.deepcopy(result) + self.current_tag_count = 0 + if self.request_redaction_paths: + self._redact_json(redacted_dict, span, self.request_redaction_paths) + if self.response_redaction_paths: + self._redact_json(redacted_dict, span, self.response_redaction_paths) + + # flatten the payload into span tags + for key2, value in redacted_dict.items(): + escaped_sub_key = key2.replace(".", "\\.") + self._tag_object(span, f"{key}.{escaped_sub_key}", value) + if self.current_tag_count >= config.botocore.get("payload_tagging_max_tags"): + return + + def _should_json_parse(self, obj: Any) -> bool: + if isinstance(obj, (str, bytes)): + return True + return False + + def _validate_json_paths(self, paths: Optional[str]) -> bool: + """ + Checks whether paths is "all" or all valid JSONPaths + """ + if not paths: + return False # not enabled + + if paths == "all": + return True # enabled, use the defaults + + # otherwise validate that we have valid JSONPaths + for path in paths.split(","): + if path: + try: + parse(path) + except Exception: + return False + else: + return False + + return True + + def _redact_json(self, data: Dict[str, Any], span: Span, paths: list) -> None: + """ + Redact sensitive data in the JSON payload based on default and user-provided JSONPath expressions + """ + for path in paths: + expression = parse(path) + for match in expression.find(data): + match.context.value[match.path.fields[0]] = "redacted" + + def _get_redaction_paths_response(self) -> list: + """ + Get the list of redaction paths, combining defaults with any user-provided JSONPaths. + """ + if not config.botocore.get("payload_tagging_response"): + return [] + + response_redaction = config.botocore.get("payload_tagging_response") + if self._validate_json_paths(response_redaction): + if response_redaction == "all": + return self._RESPONSE_REDACTION_PATHS_DEFAULTS + self._REDACTION_PATHS_DEFAULTS + return ( + self._RESPONSE_REDACTION_PATHS_DEFAULTS + self._REDACTION_PATHS_DEFAULTS + response_redaction.split(",") + ) + + return [] + + def _get_redaction_paths_request(self) -> list: + """ + Get the list of redaction paths, combining defaults with any user-provided JSONPaths. + """ + if not config.botocore.get("payload_tagging_request"): + return [] + + request_redaction = config.botocore.get("payload_tagging_request") + if self._validate_json_paths(request_redaction): + if request_redaction == "all": + return self._REQUEST_REDACTION_PATHS_DEFAULTS + self._REDACTION_PATHS_DEFAULTS + return ( + self._REQUEST_REDACTION_PATHS_DEFAULTS + self._REDACTION_PATHS_DEFAULTS + request_redaction.split(",") + ) + + return [] + + def _tag_object(self, span: Span, key: str, obj: Any, depth: int = 0) -> None: + """ + Recursively expands the given AWS payload object and adds the values as flattened Span tags. + It is not expected that AWS Payloads will be deeply nested so the number of recursive calls should be low. + For example, the following (shortened payload object) becomes: + { + "ResponseMetadata": { + "RequestId": "SOMEID", + "HTTPHeaders": { + "x-amz-request-id": "SOMEID", + "content-length": "5", + } + } + + => + + "aws.response.body.RequestId": "SOMEID" + "aws.response.body.HTTPHeaders.x-amz-request-id": "SOMEID" + "aws.response.body.HTTPHeaders.content-length": "5" + """ + # if we've hit the maximum allowed tags, mark the expansion as incomplete + if self.current_tag_count >= config.botocore.get("payload_tagging_max_tags"): + span.set_tag(self._INCOMPLETE_TAG, True) + return + if obj is None: + self.current_tag_count += 1 + span.set_tag(key, obj) + return + if depth >= config.botocore.get("payload_tagging_max_depth"): + self.current_tag_count += 1 + span.set_tag( + key, str(obj)[:_MAX_TAG_VALUE_LENGTH] + ) # at the maximum depth - set the tag without further expansion + return + depth += 1 + if self._should_json_parse(obj): + try: + parsed = json.loads(obj) + self._tag_object(span, key, parsed, depth) + except ValueError: + self.current_tag_count += 1 + span.set_tag(key, str(obj)[:_MAX_TAG_VALUE_LENGTH]) + return + if isinstance(obj, (int, float, Decimal)): + self.current_tag_count += 1 + span.set_tag(key, str(obj)) + return + if isinstance(obj, list): + for k, v in enumerate(obj): + self._tag_object(span, f"{key}.{k}", v, depth) + return + if hasattr(obj, "items"): + for k, v in obj.items(): + escaped_key = str(k).replace(".", "\\.") + self._tag_object(span, f"{key}.{escaped_key}", v, depth) + return + if hasattr(obj, "to_dict"): + for k, v in obj.to_dict().items(): + escaped_key = str(k).replace(".", "\\.") + self._tag_object(span, f"{key}.{escaped_key}", v, depth) + return + try: + value_as_str = str(obj) + except Exception: + value_as_str = "UNKNOWN" + self.current_tag_count += 1 + span.set_tag(key, value_as_str) diff --git a/ddtrace/_trace/utils_botocore/span_tags.py b/ddtrace/_trace/utils_botocore/span_tags.py index 1b40b5abcb9..5394c2b397a 100644 --- a/ddtrace/_trace/utils_botocore/span_tags.py +++ b/ddtrace/_trace/utils_botocore/span_tags.py @@ -5,6 +5,7 @@ from ddtrace import Span from ddtrace import config +from ddtrace._trace.utils_botocore.aws_payload_tagging import AWSPayloadTagging from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY from ddtrace.constants import SPAN_KIND from ddtrace.constants import SPAN_MEASURED_KEY @@ -15,6 +16,9 @@ from ddtrace.internal.utils.formats import deep_getattr +_PAYLOAD_TAGGER = AWSPayloadTagging() + + def set_botocore_patched_api_call_span_tags(span: Span, instance, args, params, endpoint_name, operation): span.set_tag_str(COMPONENT, config.botocore.integration_name) # set span.kind to the type of request being performed @@ -31,6 +35,11 @@ def set_botocore_patched_api_call_span_tags(span: Span, instance, args, params, if params and not config.botocore["tag_no_params"]: aws._add_api_param_span_tags(span, endpoint_name, params) + if config.botocore["payload_tagging_request"] and endpoint_name in config.botocore.get( + "payload_tagging_services" + ): + _PAYLOAD_TAGGER.expand_payload_as_tags(span, params, "aws.request.body") + else: span.resource = endpoint_name @@ -54,6 +63,11 @@ def set_botocore_response_metadata_tags( return response_meta = result["ResponseMetadata"] + if config.botocore["payload_tagging_response"] and span.get_tag("aws_service") in config.botocore.get( + "payload_tagging_services" + ): + _PAYLOAD_TAGGER.expand_payload_as_tags(span, response_meta, "aws.response.body") + if "HTTPStatusCode" in response_meta: status_code = response_meta["HTTPStatusCode"] span.set_tag(http.STATUS_CODE, status_code) diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py index 20de2d8cf11..febad29f982 100644 --- a/ddtrace/contrib/internal/botocore/patch.py +++ b/ddtrace/contrib/internal/botocore/patch.py @@ -104,6 +104,17 @@ def _load_dynamodb_primary_key_names_for_tables() -> Dict[str, Set[str]]: "empty_poll_enabled": asbool(os.getenv("DD_BOTOCORE_EMPTY_POLL_ENABLED", default=True)), "dynamodb_primary_key_names_for_tables": _load_dynamodb_primary_key_names_for_tables(), "add_span_pointers": asbool(os.getenv("DD_BOTOCORE_ADD_SPAN_POINTERS", default=True)), + "payload_tagging_request": os.getenv("DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING", default=None), + "payload_tagging_response": os.getenv("DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING", default=None), + "payload_tagging_max_depth": int( + os.getenv("DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH", 10) + ), # RFC defined 10 levels (1.2.3.4...10) as max tagging depth + "payload_tagging_max_tags": int( + os.getenv("DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_TAGS", 758) + ), # RFC defined default limit - spans are limited past 1000 + "payload_tagging_services": set( + os.getenv("DD_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES", default={"s3", "sns", "sqs", "kinesis", "eventbridge"}) + ), }, ) diff --git a/ddtrace/vendor/__init__.py b/ddtrace/vendor/__init__.py index 2a1f73bb472..1b9596e82da 100644 --- a/ddtrace/vendor/__init__.py +++ b/ddtrace/vendor/__init__.py @@ -91,6 +91,33 @@ Notes: - We only vendor the packaging.version sub-module as this is all we currently need. + + +ply +--------- + +Source: https://github.com/dabeaz/ply +Version: 3.11 +License: BSD-3-Clause + +Notes: + - jsonpath-ng dependency + Did a "pip install jsonpath-ng" + Then went and looked at the contents of the ply packages + yacc.py and lex.py files here. + Didn't copy: cpp.py, ctokens.py, ygen.py (didn't see them used) + + +jsonpath-ng +--------- + +Source: https://github.com/h2non/jsonpath-ng +Version: 1.6.1 +License: Apache License 2.0 + +Notes: + - Copied ply into vendors as well. + Changed "-" to "_" as was causing errors when importing. """ # Initialize `ddtrace.vendor.datadog.base.log` logger with our custom rate limited logger diff --git a/ddtrace/vendor/jsonpath_ng/__init__.py b/ddtrace/vendor/jsonpath_ng/__init__.py new file mode 100644 index 00000000000..a17f881ae61 --- /dev/null +++ b/ddtrace/vendor/jsonpath_ng/__init__.py @@ -0,0 +1,6 @@ +from .jsonpath import * # noqa +from .parser import parse # noqa + + +# Current package version +__version__ = '1.6.1' \ No newline at end of file diff --git a/ddtrace/vendor/jsonpath_ng/exceptions.py b/ddtrace/vendor/jsonpath_ng/exceptions.py new file mode 100644 index 00000000000..7714802fe93 --- /dev/null +++ b/ddtrace/vendor/jsonpath_ng/exceptions.py @@ -0,0 +1,10 @@ +class JSONPathError(Exception): + pass + + +class JsonPathLexerError(JSONPathError): + pass + + +class JsonPathParserError(JSONPathError): + pass \ No newline at end of file diff --git a/ddtrace/vendor/jsonpath_ng/jsonpath.py b/ddtrace/vendor/jsonpath_ng/jsonpath.py new file mode 100644 index 00000000000..683ae65a230 --- /dev/null +++ b/ddtrace/vendor/jsonpath_ng/jsonpath.py @@ -0,0 +1,815 @@ +import logging +from itertools import * # noqa +from .lexer import JsonPathLexer + +# Get logger name +logger = logging.getLogger(__name__) + +# Turn on/off the automatic creation of id attributes +# ... could be a kwarg pervasively but uses are rare and simple today +auto_id_field = None + +NOT_SET = object() +LIST_KEY = object() + + +class JSONPath: + """ + The base class for JSONPath abstract syntax; those + methods stubbed here are the interface to supported + JSONPath semantics. + """ + + def find(self, data): + """ + All `JSONPath` types support `find()`, which returns an iterable of `DatumInContext`s. + They keep track of the path followed to the current location, so if the calling code + has some opinion about that, it can be passed in here as a starting point. + """ + raise NotImplementedError() + + def find_or_create(self, data): + return self.find(data) + + def update(self, data, val): + """ + Returns `data` with the specified path replaced by `val`. Only updates + if the specified path exists. + """ + + raise NotImplementedError() + + def update_or_create(self, data, val): + return self.update(data, val) + + def filter(self, fn, data): + """ + Returns `data` with the specified path filtering nodes according + the filter evaluation result returned by the filter function. + + Arguments: + fn (function): unary function that accepts one argument + and returns bool. + data (dict|list|tuple): JSON object to filter. + """ + + raise NotImplementedError() + + def child(self, child): + """ + Equivalent to Child(self, next) but with some canonicalization + """ + if isinstance(self, This) or isinstance(self, Root): + return child + elif isinstance(child, This): + return self + elif isinstance(child, Root): + return child + else: + return Child(self, child) + + def make_datum(self, value): + if isinstance(value, DatumInContext): + return value + else: + return DatumInContext(value, path=Root(), context=None) + + +class DatumInContext: + """ + Represents a datum along a path from a context. + + Essentially a zipper but with a structure represented by JsonPath, + and where the context is more of a parent pointer than a proper + representation of the context. + + For quick-and-dirty work, this proxies any non-special attributes + to the underlying datum, but the actual datum can (and usually should) + be retrieved via the `value` attribute. + + To place `datum` within another, use `datum.in_context(context=..., path=...)` + which extends the path. If the datum already has a context, it places the entire + context within that passed in, so an object can be built from the inside + out. + """ + @classmethod + def wrap(cls, data): + if isinstance(data, cls): + return data + else: + return cls(data) + + def __init__(self, value, path=None, context=None): + self.value = value + self.path = path or This() + self.context = None if context is None else DatumInContext.wrap(context) + + def in_context(self, context, path): + context = DatumInContext.wrap(context) + + if self.context: + return DatumInContext(value=self.value, path=self.path, context=context.in_context(path=path, context=context)) + else: + return DatumInContext(value=self.value, path=path, context=context) + + @property + def full_path(self): + return self.path if self.context is None else self.context.full_path.child(self.path) + + @property + def id_pseudopath(self): + """ + Looks like a path, but with ids stuck in when available + """ + try: + pseudopath = Fields(str(self.value[auto_id_field])) + except (TypeError, AttributeError, KeyError): # This may not be all the interesting exceptions + pseudopath = self.path + + if self.context: + return self.context.id_pseudopath.child(pseudopath) + else: + return pseudopath + + def __repr__(self): + return '%s(value=%r, path=%r, context=%r)' % (self.__class__.__name__, self.value, self.path, self.context) + + def __eq__(self, other): + return isinstance(other, DatumInContext) and other.value == self.value and other.path == self.path and self.context == other.context + + +class AutoIdForDatum(DatumInContext): + """ + This behaves like a DatumInContext, but the value is + always the path leading up to it, not including the "id", + and with any "id" fields along the way replacing the prior + segment of the path + + For example, it will make "foo.bar.id" return a datum + that behaves like DatumInContext(value="foo.bar", path="foo.bar.id"). + + This is disabled by default; it can be turned on by + settings the `auto_id_field` global to a value other + than `None`. + """ + + def __init__(self, datum, id_field=None): + """ + Invariant is that datum.path is the path from context to datum. The auto id + will either be the id in the datum (if present) or the id of the context + followed by the path to the datum. + + The path to this datum is always the path to the context, the path to the + datum, and then the auto id field. + """ + self.datum = datum + self.id_field = id_field or auto_id_field + + @property + def value(self): + return str(self.datum.id_pseudopath) + + @property + def path(self): + return self.id_field + + @property + def context(self): + return self.datum + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.datum) + + def in_context(self, context, path): + return AutoIdForDatum(self.datum.in_context(context=context, path=path)) + + def __eq__(self, other): + return isinstance(other, AutoIdForDatum) and other.datum == self.datum and self.id_field == other.id_field + + +class Root(JSONPath): + """ + The JSONPath referring to the "root" object. Concrete syntax is '$'. + The root is the topmost datum without any context attached. + """ + + def find(self, data): + if not isinstance(data, DatumInContext): + return [DatumInContext(data, path=Root(), context=None)] + else: + if data.context is None: + return [DatumInContext(data.value, context=None, path=Root())] + else: + return Root().find(data.context) + + def update(self, data, val): + return val + + def filter(self, fn, data): + return data if fn(data) else None + + def __str__(self): + return '$' + + def __repr__(self): + return 'Root()' + + def __eq__(self, other): + return isinstance(other, Root) + + def __hash__(self): + return hash('$') + + +class This(JSONPath): + """ + The JSONPath referring to the current datum. Concrete syntax is '@'. + """ + + def find(self, datum): + return [DatumInContext.wrap(datum)] + + def update(self, data, val): + return val + + def filter(self, fn, data): + return data if fn(data) else None + + def __str__(self): + return '`this`' + + def __repr__(self): + return 'This()' + + def __eq__(self, other): + return isinstance(other, This) + + def __hash__(self): + return hash('this') + + +class Child(JSONPath): + """ + JSONPath that first matches the left, then the right. + Concrete syntax is '.' + """ + + def __init__(self, left, right): + self.left = left + self.right = right + + def find(self, datum): + """ + Extra special case: auto ids do not have children, + so cut it off right now rather than auto id the auto id + """ + + return [submatch + for subdata in self.left.find(datum) + if not isinstance(subdata, AutoIdForDatum) + for submatch in self.right.find(subdata)] + + def update(self, data, val): + for datum in self.left.find(data): + self.right.update(datum.value, val) + return data + + def find_or_create(self, datum): + datum = DatumInContext.wrap(datum) + submatches = [] + for subdata in self.left.find_or_create(datum): + if isinstance(subdata, AutoIdForDatum): + # Extra special case: auto ids do not have children, + # so cut it off right now rather than auto id the auto id + continue + for submatch in self.right.find_or_create(subdata): + submatches.append(submatch) + return submatches + + def update_or_create(self, data, val): + for datum in self.left.find_or_create(data): + self.right.update_or_create(datum.value, val) + return _clean_list_keys(data) + + def filter(self, fn, data): + for datum in self.left.find(data): + self.right.filter(fn, datum.value) + return data + + def __eq__(self, other): + return isinstance(other, Child) and self.left == other.left and self.right == other.right + + def __str__(self): + return '%s.%s' % (self.left, self.right) + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right) + + def __hash__(self): + return hash((self.left, self.right)) + + +class Parent(JSONPath): + """ + JSONPath that matches the parent node of the current match. + Will crash if no such parent exists. + Available via named operator `parent`. + """ + + def find(self, datum): + datum = DatumInContext.wrap(datum) + return [datum.context] + + def __eq__(self, other): + return isinstance(other, Parent) + + def __str__(self): + return '`parent`' + + def __repr__(self): + return 'Parent()' + + def __hash__(self): + return hash('parent') + + +class Where(JSONPath): + """ + JSONPath that first matches the left, and then + filters for only those nodes that have + a match on the right. + + WARNING: Subject to change. May want to have "contains" + or some other better word for it. + """ + + def __init__(self, left, right): + self.left = left + self.right = right + + def find(self, data): + return [subdata for subdata in self.left.find(data) if self.right.find(subdata)] + + def update(self, data, val): + for datum in self.find(data): + datum.path.update(data, val) + return data + + def filter(self, fn, data): + for datum in self.find(data): + datum.path.filter(fn, datum.value) + return data + + def __str__(self): + return '%s where %s' % (self.left, self.right) + + def __eq__(self, other): + return isinstance(other, Where) and other.left == self.left and other.right == self.right + + def __hash__(self): + return hash((self.left, self.right)) + +class Descendants(JSONPath): + """ + JSONPath that matches first the left expression then any descendant + of it which matches the right expression. + """ + + def __init__(self, left, right): + self.left = left + self.right = right + + def find(self, datum): + # .. ==> . ( | *.. | [*]..) + # + # With with a wonky caveat that since Slice() has funky coercions + # we cannot just delegate to that equivalence or we'll hit an + # infinite loop. So right here we implement the coercion-free version. + + # Get all left matches into a list + left_matches = self.left.find(datum) + if not isinstance(left_matches, list): + left_matches = [left_matches] + + def match_recursively(datum): + right_matches = self.right.find(datum) + + # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern + if isinstance(datum.value, list): + recursive_matches = [submatch + for i in range(0, len(datum.value)) + for submatch in match_recursively(DatumInContext(datum.value[i], context=datum, path=Index(i)))] + + elif isinstance(datum.value, dict): + recursive_matches = [submatch + for field in datum.value.keys() + for submatch in match_recursively(DatumInContext(datum.value[field], context=datum, path=Fields(field)))] + + else: + recursive_matches = [] + + return right_matches + list(recursive_matches) + + # TODO: repeatable iterator instead of list? + return [submatch + for left_match in left_matches + for submatch in match_recursively(left_match)] + + def is_singular(self): + return False + + def update(self, data, val): + # Get all left matches into a list + left_matches = self.left.find(data) + if not isinstance(left_matches, list): + left_matches = [left_matches] + + def update_recursively(data): + # Update only mutable values corresponding to JSON types + if not (isinstance(data, list) or isinstance(data, dict)): + return + + self.right.update(data, val) + + # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern + if isinstance(data, list): + for i in range(0, len(data)): + update_recursively(data[i]) + + elif isinstance(data, dict): + for field in data.keys(): + update_recursively(data[field]) + + for submatch in left_matches: + update_recursively(submatch.value) + + return data + + def filter(self, fn, data): + # Get all left matches into a list + left_matches = self.left.find(data) + if not isinstance(left_matches, list): + left_matches = [left_matches] + + def filter_recursively(data): + # Update only mutable values corresponding to JSON types + if not (isinstance(data, list) or isinstance(data, dict)): + return + + self.right.filter(fn, data) + + # Manually do the * or [*] to avoid coercion and recurse just the right-hand pattern + if isinstance(data, list): + for i in range(0, len(data)): + filter_recursively(data[i]) + + elif isinstance(data, dict): + for field in data.keys(): + filter_recursively(data[field]) + + for submatch in left_matches: + filter_recursively(submatch.value) + + return data + + def __str__(self): + return '%s..%s' % (self.left, self.right) + + def __eq__(self, other): + return isinstance(other, Descendants) and self.left == other.left and self.right == other.right + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right) + + def __hash__(self): + return hash((self.left, self.right)) + + +class Union(JSONPath): + """ + JSONPath that returns the union of the results of each match. + This is pretty shoddily implemented for now. The nicest semantics + in case of mismatched bits (list vs atomic) is to put + them all in a list, but I haven't done that yet. + + WARNING: Any appearance of this being the _concatenation_ is + coincidence. It may even be a bug! (or laziness) + """ + def __init__(self, left, right): + self.left = left + self.right = right + + def is_singular(self): + return False + + def find(self, data): + return self.left.find(data) + self.right.find(data) + + def __eq__(self, other): + return isinstance(other, Union) and self.left == other.left and self.right == other.right + + def __hash__(self): + return hash((self.left, self.right)) + +class Intersect(JSONPath): + """ + JSONPath for bits that match *both* patterns. + + This can be accomplished a couple of ways. The most + efficient is to actually build the intersected + AST as in building a state machine for matching the + intersection of regular languages. The next + idea is to build a filtered data and match against + that. + """ + def __init__(self, left, right): + self.left = left + self.right = right + + def is_singular(self): + return False + + def find(self, data): + raise NotImplementedError() + + def __eq__(self, other): + return isinstance(other, Intersect) and self.left == other.left and self.right == other.right + + def __hash__(self): + return hash((self.left, self.right)) + + +class Fields(JSONPath): + """ + JSONPath referring to some field of the current object. + Concrete syntax ix comma-separated field names. + + WARNING: If '*' is any of the field names, then they will + all be returned. + """ + + def __init__(self, *fields): + self.fields = fields + + @staticmethod + def get_field_datum(datum, field, create): + if field == auto_id_field: + return AutoIdForDatum(datum) + try: + field_value = datum.value.get(field, NOT_SET) + if field_value is NOT_SET: + if create: + datum.value[field] = field_value = {} + else: + return None + return DatumInContext(field_value, path=Fields(field), context=datum) + except (TypeError, AttributeError): + return None + + def reified_fields(self, datum): + if '*' not in self.fields: + return self.fields + else: + try: + fields = tuple(datum.value.keys()) + return fields if auto_id_field is None else fields + (auto_id_field,) + except AttributeError: + return () + + def find(self, datum): + return self._find_base(datum, create=False) + + def find_or_create(self, datum): + return self._find_base(datum, create=True) + + def _find_base(self, datum, create): + datum = DatumInContext.wrap(datum) + field_data = [self.get_field_datum(datum, field, create) + for field in self.reified_fields(datum)] + return [fd for fd in field_data if fd is not None] + + def update(self, data, val): + return self._update_base(data, val, create=False) + + def update_or_create(self, data, val): + return self._update_base(data, val, create=True) + + def _update_base(self, data, val, create): + if data is not None: + for field in self.reified_fields(DatumInContext.wrap(data)): + if create and field not in data: + data[field] = {} + if type(data) is not bool and field in data: + if hasattr(val, '__call__'): + data[field] = val(data[field], data, field) + else: + data[field] = val + return data + + def filter(self, fn, data): + if data is not None: + for field in self.reified_fields(DatumInContext.wrap(data)): + if field in data: + if fn(data[field]): + data.pop(field) + return data + + def __str__(self): + # If any JsonPathLexer.literals are included in field name need quotes + # This avoids unnecessary quotes to keep strings short. + # Test each field whether it contains a literal and only then add quotes + # The test loops over all literals, could possibly optimize to short circuit if one found + fields_as_str = ("'" + str(f) + "'" if any([l in f for l in JsonPathLexer.literals]) else + str(f) for f in self.fields) + return ','.join(fields_as_str) + + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, ','.join(map(repr, self.fields))) + + def __eq__(self, other): + return isinstance(other, Fields) and tuple(self.fields) == tuple(other.fields) + + def __hash__(self): + return hash(tuple(self.fields)) + + +class Index(JSONPath): + """ + JSONPath that matches indices of the current datum, or none if not large enough. + Concrete syntax is brackets. + + WARNING: If the datum is None or not long enough, it will not crash but will not match anything. + NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]` + """ + + def __init__(self, index): + self.index = index + + def find(self, datum): + return self._find_base(datum, create=False) + + def find_or_create(self, datum): + return self._find_base(datum, create=True) + + def _find_base(self, datum, create): + datum = DatumInContext.wrap(datum) + if create: + if datum.value == {}: + datum.value = _create_list_key(datum.value) + self._pad_value(datum.value) + if datum.value and len(datum.value) > self.index: + return [DatumInContext(datum.value[self.index], path=self, context=datum)] + else: + return [] + + def update(self, data, val): + return self._update_base(data, val, create=False) + + def update_or_create(self, data, val): + return self._update_base(data, val, create=True) + + def _update_base(self, data, val, create): + if create: + if data == {}: + data = _create_list_key(data) + self._pad_value(data) + if hasattr(val, '__call__'): + data[self.index] = val.__call__(data[self.index], data, self.index) + elif len(data) > self.index: + data[self.index] = val + return data + + def filter(self, fn, data): + if fn(data[self.index]): + data.pop(self.index) # relies on mutation :( + return data + + def __eq__(self, other): + return isinstance(other, Index) and self.index == other.index + + def __str__(self): + return '[%i]' % self.index + + def __repr__(self): + return '%s(index=%r)' % (self.__class__.__name__, self.index) + + def _pad_value(self, value): + if len(value) <= self.index: + pad = self.index - len(value) + 1 + value += [{} for __ in range(pad)] + + def __hash__(self): + return hash(self.index) + + +class Slice(JSONPath): + """ + JSONPath matching a slice of an array. + + Because of a mismatch between JSON and XML when schema-unaware, + this always returns an iterable; if the incoming data + was not a list, then it returns a one element list _containing_ that + data. + + Consider these two docs, and their schema-unaware translation to JSON: + + hello ==> {"a": {"b": "hello"}} + hellogoodbye ==> {"a": {"b": ["hello", "goodbye"]}} + + If there were a schema, it would be known that "b" should always be an + array (unless the schema were wonky, but that is too much to fix here) + so when querying with JSON if the one writing the JSON knows that it + should be an array, they can write a slice operator and it will coerce + a non-array value to an array. + + This may be a bit unfortunate because it would be nice to always have + an iterator, but dictionaries and other objects may also be iterable, + so this is the compromise. + """ + def __init__(self, start=None, end=None, step=None): + self.start = start + self.end = end + self.step = step + + def find(self, datum): + datum = DatumInContext.wrap(datum) + + # Used for catching null value instead of empty list in path + if not datum.value: + return [] + # Here's the hack. If it is a dictionary or some kind of constant, + # put it in a single-element list + if (isinstance(datum.value, dict) or isinstance(datum.value, int) or isinstance(datum.value, str)): + return self.find(DatumInContext([datum.value], path=datum.path, context=datum.context)) + + # Some iterators do not support slicing but we can still + # at least work for '*' + if self.start is None and self.end is None and self.step is None: + return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))] + else: + return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]] + + def update(self, data, val): + for datum in self.find(data): + datum.path.update(data, val) + return data + + def filter(self, fn, data): + while True: + length = len(data) + for datum in self.find(data): + data = datum.path.filter(fn, data) + if len(data) < length: + break + + if length == len(data): + break + return data + + def __str__(self): + if self.start is None and self.end is None and self.step is None: + return '[*]' + else: + return '[%s%s%s]' % (self.start or '', + ':%d'%self.end if self.end else '', + ':%d'%self.step if self.step else '') + + def __repr__(self): + return '%s(start=%r,end=%r,step=%r)' % (self.__class__.__name__, self.start, self.end, self.step) + + def __eq__(self, other): + return isinstance(other, Slice) and other.start == self.start and self.end == other.end and other.step == self.step + + def __hash__(self): + return hash((self.start, self.end, self.step)) + + +def _create_list_key(dict_): + """ + Adds a list to a dictionary by reference and returns the list. + + See `_clean_list_keys()` + """ + dict_[LIST_KEY] = new_list = [{}] + return new_list + + +def _clean_list_keys(struct_): + """ + Replace {LIST_KEY: ['foo', 'bar']} with ['foo', 'bar']. + + >>> _clean_list_keys({LIST_KEY: ['foo', 'bar']}) + ['foo', 'bar'] + + """ + if(isinstance(struct_, list)): + for ind, value in enumerate(struct_): + struct_[ind] = _clean_list_keys(value) + elif(isinstance(struct_, dict)): + if(LIST_KEY in struct_): + return _clean_list_keys(struct_[LIST_KEY]) + else: + for key, value in struct_.items(): + struct_[key] = _clean_list_keys(value) + return struct_ \ No newline at end of file diff --git a/ddtrace/vendor/jsonpath_ng/lexer.py b/ddtrace/vendor/jsonpath_ng/lexer.py new file mode 100644 index 00000000000..055447a5bd9 --- /dev/null +++ b/ddtrace/vendor/jsonpath_ng/lexer.py @@ -0,0 +1,171 @@ +import sys +import logging + +from ..ply.lex import lex + +from .exceptions import JsonPathLexerError + +logger = logging.getLogger(__name__) + + +class JsonPathLexer: + ''' + A Lexical analyzer for JsonPath. + ''' + + def __init__(self, debug=False): + self.debug = debug + if self.__doc__ is None: + raise JsonPathLexerError('Docstrings have been removed! By design of PLY, jsonpath-rw requires docstrings. You must not use PYTHONOPTIMIZE=2 or python -OO.') + + def tokenize(self, string): + ''' + Maps a string to an iterator over tokens. In other words: [char] -> [token] + ''' + + new_lexer = lex(module=self, debug=self.debug, errorlog=logger) + new_lexer.latest_newline = 0 + new_lexer.string_value = None + new_lexer.input(string) + + while True: + t = new_lexer.token() + if t is None: + break + t.col = t.lexpos - new_lexer.latest_newline + yield t + + if new_lexer.string_value is not None: + raise JsonPathLexerError('Unexpected EOF in string literal or identifier') + + # ============== PLY Lexer specification ================== + # + # This probably should be private but: + # - the parser requires access to `tokens` (perhaps they should be defined in a third, shared dependency) + # - things like `literals` might be a legitimate part of the public interface. + # + # Anyhow, it is pythonic to give some rope to hang oneself with :-) + + literals = ['*', '.', '[', ']', '(', ')', '$', ',', ':', '|', '&', '~'] + + reserved_words = { 'where': 'WHERE' } + + tokens = ['DOUBLEDOT', 'NUMBER', 'ID', 'NAMED_OPERATOR'] + list(reserved_words.values()) + + states = [ ('singlequote', 'exclusive'), + ('doublequote', 'exclusive'), + ('backquote', 'exclusive') ] + + # Normal lexing, rather easy + t_DOUBLEDOT = r'\.\.' + t_ignore = ' \t' + + def t_ID(self, t): + r'[a-zA-Z_@][a-zA-Z0-9_@\-]*' + t.type = self.reserved_words.get(t.value, 'ID') + return t + + def t_NUMBER(self, t): + r'-?\d+' + t.value = int(t.value) + return t + + + # Single-quoted strings + t_singlequote_ignore = '' + def t_singlequote(self, t): + r"'" + t.lexer.string_start = t.lexer.lexpos + t.lexer.string_value = '' + t.lexer.push_state('singlequote') + + def t_singlequote_content(self, t): + r"[^'\\]+" + t.lexer.string_value += t.value + + def t_singlequote_escape(self, t): + r'\\.' + t.lexer.string_value += t.value[1] + + def t_singlequote_end(self, t): + r"'" + t.value = t.lexer.string_value + t.type = 'ID' + t.lexer.string_value = None + t.lexer.pop_state() + return t + + def t_singlequote_error(self, t): + raise JsonPathLexerError('Error on line %s, col %s while lexing singlequoted field: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0])) + + + # Double-quoted strings + t_doublequote_ignore = '' + def t_doublequote(self, t): + r'"' + t.lexer.string_start = t.lexer.lexpos + t.lexer.string_value = '' + t.lexer.push_state('doublequote') + + def t_doublequote_content(self, t): + r'[^"\\]+' + t.lexer.string_value += t.value + + def t_doublequote_escape(self, t): + r'\\.' + t.lexer.string_value += t.value[1] + + def t_doublequote_end(self, t): + r'"' + t.value = t.lexer.string_value + t.type = 'ID' + t.lexer.string_value = None + t.lexer.pop_state() + return t + + def t_doublequote_error(self, t): + raise JsonPathLexerError('Error on line %s, col %s while lexing doublequoted field: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0])) + + + # Back-quoted "magic" operators + t_backquote_ignore = '' + def t_backquote(self, t): + r'`' + t.lexer.string_start = t.lexer.lexpos + t.lexer.string_value = '' + t.lexer.push_state('backquote') + + def t_backquote_escape(self, t): + r'\\.' + t.lexer.string_value += t.value[1] + + def t_backquote_content(self, t): + r"[^`\\]+" + t.lexer.string_value += t.value + + def t_backquote_end(self, t): + r'`' + t.value = t.lexer.string_value + t.type = 'NAMED_OPERATOR' + t.lexer.string_value = None + t.lexer.pop_state() + return t + + def t_backquote_error(self, t): + raise JsonPathLexerError('Error on line %s, col %s while lexing backquoted operator: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0])) + + + # Counting lines, handling errors + def t_newline(self, t): + r'\n' + t.lexer.lineno += 1 + t.lexer.latest_newline = t.lexpos + + def t_error(self, t): + raise JsonPathLexerError('Error on line %s, col %s: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.lexer.latest_newline, t.value[0])) + +if __name__ == '__main__': + logging.basicConfig() + lexer = JsonPathLexer(debug=True) + for token in lexer.tokenize(sys.stdin.read()): + print('%-20s%s' % (token.value, token.type)) \ No newline at end of file diff --git a/ddtrace/vendor/jsonpath_ng/parser.py b/ddtrace/vendor/jsonpath_ng/parser.py new file mode 100644 index 00000000000..755916bda89 --- /dev/null +++ b/ddtrace/vendor/jsonpath_ng/parser.py @@ -0,0 +1,198 @@ +import logging +import sys +import os.path + +from ..ply.yacc import yacc + +from .exceptions import JsonPathParserError +from .jsonpath import * +from .lexer import JsonPathLexer + +logger = logging.getLogger(__name__) + + +def parse(string): + return JsonPathParser().parse(string) + + +class JsonPathParser: + ''' + An LALR-parser for JsonPath + ''' + + tokens = JsonPathLexer.tokens + + def __init__(self, debug=False, lexer_class=None): + if self.__doc__ is None: + raise JsonPathParserError( + 'Docstrings have been removed! By design of PLY, ' + 'jsonpath-rw requires docstrings. You must not use ' + 'PYTHONOPTIMIZE=2 or python -OO.' + ) + + self.debug = debug + self.lexer_class = lexer_class or JsonPathLexer # Crufty but works around statefulness in PLY + + # Since PLY has some crufty aspects and dumps files, we try to keep them local + # However, we need to derive the name of the output Python file :-/ + output_directory = os.path.dirname(__file__) + try: + module_name = os.path.splitext(os.path.split(__file__)[1])[0] + except: + module_name = __name__ + + start_symbol = 'jsonpath' + parsing_table_module = '_'.join([module_name, start_symbol, 'parsetab']) + + # Generate the parse table + self.parser = yacc(module=self, + debug=self.debug, + tabmodule = parsing_table_module, + outputdir = output_directory, + write_tables=0, + start = start_symbol, + errorlog = logger) + + def parse(self, string, lexer = None): + lexer = lexer or self.lexer_class() + return self.parse_token_stream(lexer.tokenize(string)) + + def parse_token_stream(self, token_iterator): + return self.parser.parse(lexer = IteratorToTokenStream(token_iterator)) + + # ===================== PLY Parser specification ===================== + + precedence = [ + ('left', ','), + ('left', 'DOUBLEDOT'), + ('left', '.'), + ('left', '|'), + ('left', '&'), + ('left', 'WHERE'), + ] + + def p_error(self, t): + if t is None: + raise JsonPathParserError('Parse error near the end of string!') + raise JsonPathParserError('Parse error at %s:%s near token %s (%s)' + % (t.lineno, t.col, t.value, t.type)) + + def p_jsonpath_binop(self, p): + """jsonpath : jsonpath '.' jsonpath + | jsonpath DOUBLEDOT jsonpath + | jsonpath WHERE jsonpath + | jsonpath '|' jsonpath + | jsonpath '&' jsonpath""" + op = p[2] + + if op == '.': + p[0] = Child(p[1], p[3]) + elif op == '..': + p[0] = Descendants(p[1], p[3]) + elif op == 'where': + p[0] = Where(p[1], p[3]) + elif op == '|': + p[0] = Union(p[1], p[3]) + elif op == '&': + p[0] = Intersect(p[1], p[3]) + + def p_jsonpath_fields(self, p): + "jsonpath : fields_or_any" + p[0] = Fields(*p[1]) + + def p_jsonpath_named_operator(self, p): + "jsonpath : NAMED_OPERATOR" + if p[1] == 'this': + p[0] = This() + elif p[1] == 'parent': + p[0] = Parent() + else: + raise JsonPathParserError('Unknown named operator `%s` at %s:%s' + % (p[1], p.lineno(1), p.lexpos(1))) + + def p_jsonpath_root(self, p): + "jsonpath : '$'" + p[0] = Root() + + def p_jsonpath_idx(self, p): + "jsonpath : '[' idx ']'" + p[0] = p[2] + + def p_jsonpath_slice(self, p): + "jsonpath : '[' slice ']'" + p[0] = p[2] + + def p_jsonpath_fieldbrackets(self, p): + "jsonpath : '[' fields ']'" + p[0] = Fields(*p[2]) + + def p_jsonpath_child_fieldbrackets(self, p): + "jsonpath : jsonpath '[' fields ']'" + p[0] = Child(p[1], Fields(*p[3])) + + def p_jsonpath_child_idxbrackets(self, p): + "jsonpath : jsonpath '[' idx ']'" + p[0] = Child(p[1], p[3]) + + def p_jsonpath_child_slicebrackets(self, p): + "jsonpath : jsonpath '[' slice ']'" + p[0] = Child(p[1], p[3]) + + def p_jsonpath_parens(self, p): + "jsonpath : '(' jsonpath ')'" + p[0] = p[2] + + # Because fields in brackets cannot be '*' - that is reserved for array indices + def p_fields_or_any(self, p): + """fields_or_any : fields + | '*' """ + if p[1] == '*': + p[0] = ['*'] + else: + p[0] = p[1] + + def p_fields_id(self, p): + "fields : ID" + p[0] = [p[1]] + + def p_fields_comma(self, p): + "fields : fields ',' fields" + p[0] = p[1] + p[3] + + def p_idx(self, p): + "idx : NUMBER" + p[0] = Index(p[1]) + + def p_slice_any(self, p): + "slice : '*'" + p[0] = Slice() + + def p_slice(self, p): # Currently does not support `step` + """slice : maybe_int ':' maybe_int + | maybe_int ':' maybe_int ':' maybe_int """ + p[0] = Slice(*p[1::2]) + + def p_maybe_int(self, p): + """maybe_int : NUMBER + | empty""" + p[0] = p[1] + + def p_empty(self, p): + 'empty :' + p[0] = None + +class IteratorToTokenStream: + def __init__(self, iterator): + self.iterator = iterator + + def token(self): + try: + return next(self.iterator) + except StopIteration: + return None + + +if __name__ == '__main__': + logging.basicConfig() + parser = JsonPathParser(debug=True) + print(parser.parse(sys.stdin.read())) \ No newline at end of file diff --git a/ddtrace/vendor/ply/__init__.py b/ddtrace/vendor/ply/__init__.py new file mode 100644 index 00000000000..23707c63541 --- /dev/null +++ b/ddtrace/vendor/ply/__init__.py @@ -0,0 +1,5 @@ +# PLY package +# Author: David Beazley (dave@dabeaz.com) + +__version__ = '3.11' +__all__ = ['lex','yacc'] diff --git a/ddtrace/vendor/ply/lex.py b/ddtrace/vendor/ply/lex.py new file mode 100644 index 00000000000..f95bcdbf1bb --- /dev/null +++ b/ddtrace/vendor/ply/lex.py @@ -0,0 +1,1098 @@ +# ----------------------------------------------------------------------------- +# ply: lex.py +# +# Copyright (C) 2001-2018 +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- + +__version__ = '3.11' +__tabversion__ = '3.10' + +import re +import sys +import types +import copy +import os +import inspect + +# This tuple contains known string types +try: + # Python 2.6 + StringTypes = (types.StringType, types.UnicodeType) +except AttributeError: + # Python 3.0 + StringTypes = (str, bytes) + +# This regular expression is used to match valid token names +_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') + +# Exception thrown when invalid token encountered and no default error +# handler is defined. +class LexError(Exception): + def __init__(self, message, s): + self.args = (message,) + self.text = s + + +# Token class. This class is used to represent the tokens produced. +class LexToken(object): + def __str__(self): + return 'LexToken(%s,%r,%d,%d)' % (self.type, self.value, self.lineno, self.lexpos) + + def __repr__(self): + return str(self) + + +# This object is a stand-in for a logging object created by the +# logging module. + +class PlyLogger(object): + def __init__(self, f): + self.f = f + + def critical(self, msg, *args, **kwargs): + self.f.write((msg % args) + '\n') + + def warning(self, msg, *args, **kwargs): + self.f.write('WARNING: ' + (msg % args) + '\n') + + def error(self, msg, *args, **kwargs): + self.f.write('ERROR: ' + (msg % args) + '\n') + + info = critical + debug = critical + + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self, name): + return self + + def __call__(self, *args, **kwargs): + return self + + +# ----------------------------------------------------------------------------- +# === Lexing Engine === +# +# The following Lexer class implements the lexer runtime. There are only +# a few public methods and attributes: +# +# input() - Store a new string in the lexer +# token() - Get the next token +# clone() - Clone the lexer +# +# lineno - Current line number +# lexpos - Current position in the input string +# ----------------------------------------------------------------------------- + +class Lexer: + def __init__(self): + self.lexre = None # Master regular expression. This is a list of + # tuples (re, findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules + self.lexretext = None # Current regular expression strings + self.lexstatere = {} # Dictionary mapping lexer states to master regexs + self.lexstateretext = {} # Dictionary mapping lexer states to regex strings + self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names + self.lexstate = 'INITIAL' # Current lexer state + self.lexstatestack = [] # Stack of lexer states + self.lexstateinfo = None # State information + self.lexstateignore = {} # Dictionary of ignored characters for each state + self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexstateeoff = {} # Dictionary of eof functions for each state + self.lexreflags = 0 # Optional re compile flags + self.lexdata = None # Actual input data (as a string) + self.lexpos = 0 # Current position in input text + self.lexlen = 0 # Length of the input text + self.lexerrorf = None # Error rule (if any) + self.lexeoff = None # EOF rule (if any) + self.lextokens = None # List of valid tokens + self.lexignore = '' # Ignored characters + self.lexliterals = '' # Literal characters that can be passed through + self.lexmodule = None # Module + self.lineno = 1 # Current line number + self.lexoptimize = False # Optimized mode + + def clone(self, object=None): + c = copy.copy(self) + + # If the object parameter has been supplied, it means we are attaching the + # lexer to a new object. In this case, we have to rebind all methods in + # the lexstatere and lexstateerrorf tables. + + if object: + newtab = {} + for key, ritem in self.lexstatere.items(): + newre = [] + for cre, findex in ritem: + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object, f[0].__name__), f[1])) + newre.append((cre, newfindex)) + newtab[key] = newre + c.lexstatere = newtab + c.lexstateerrorf = {} + for key, ef in self.lexstateerrorf.items(): + c.lexstateerrorf[key] = getattr(object, ef.__name__) + c.lexmodule = object + return c + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self, lextab, outputdir=''): + if isinstance(lextab, types.ModuleType): + raise IOError("Won't overwrite existing lextab module") + basetabmodule = lextab.split('.')[-1] + filename = os.path.join(outputdir, basetabmodule) + '.py' + with open(filename, 'w') as tf: + tf.write('# %s.py. This file automatically created by PLY (version %s). Don\'t edit!\n' % (basetabmodule, __version__)) + tf.write('_tabversion = %s\n' % repr(__tabversion__)) + tf.write('_lextokens = set(%s)\n' % repr(tuple(sorted(self.lextokens)))) + tf.write('_lexreflags = %s\n' % repr(int(self.lexreflags))) + tf.write('_lexliterals = %s\n' % repr(self.lexliterals)) + tf.write('_lexstateinfo = %s\n' % repr(self.lexstateinfo)) + + # Rewrite the lexstatere table, replacing function objects with function names + tabre = {} + for statename, lre in self.lexstatere.items(): + titem = [] + for (pat, func), retext, renames in zip(lre, self.lexstateretext[statename], self.lexstaterenames[statename]): + titem.append((retext, _funcs_to_names(func, renames))) + tabre[statename] = titem + + tf.write('_lexstatere = %s\n' % repr(tabre)) + tf.write('_lexstateignore = %s\n' % repr(self.lexstateignore)) + + taberr = {} + for statename, ef in self.lexstateerrorf.items(): + taberr[statename] = ef.__name__ if ef else None + tf.write('_lexstateerrorf = %s\n' % repr(taberr)) + + tabeof = {} + for statename, ef in self.lexstateeoff.items(): + tabeof[statename] = ef.__name__ if ef else None + tf.write('_lexstateeoff = %s\n' % repr(tabeof)) + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self, tabfile, fdict): + if isinstance(tabfile, types.ModuleType): + lextab = tabfile + else: + exec('import %s' % tabfile) + lextab = sys.modules[tabfile] + + if getattr(lextab, '_tabversion', '0.0') != __tabversion__: + raise ImportError('Inconsistent PLY version') + + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lextokens_all = self.lextokens | set(self.lexliterals) + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = {} + self.lexstateretext = {} + for statename, lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for pat, func_name in lre: + titem.append((re.compile(pat, lextab._lexreflags), _names_to_funcs(func_name, fdict))) + + self.lexstatere[statename] = titem + self.lexstateretext[statename] = txtitem + + self.lexstateerrorf = {} + for statename, ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[statename] = fdict[ef] + + self.lexstateeoff = {} + for statename, ef in lextab._lexstateeoff.items(): + self.lexstateeoff[statename] = fdict[ef] + + self.begin('INITIAL') + + # ------------------------------------------------------------ + # input() - Push a new string into the lexer + # ------------------------------------------------------------ + def input(self, s): + # Pull off the first character to see if s looks like a string + c = s[:1] + if not isinstance(c, StringTypes): + raise ValueError('Expected a string') + self.lexdata = s + self.lexpos = 0 + self.lexlen = len(s) + + # ------------------------------------------------------------ + # begin() - Changes the lexing state + # ------------------------------------------------------------ + def begin(self, state): + if state not in self.lexstatere: + raise ValueError('Undefined state') + self.lexre = self.lexstatere[state] + self.lexretext = self.lexstateretext[state] + self.lexignore = self.lexstateignore.get(state, '') + self.lexerrorf = self.lexstateerrorf.get(state, None) + self.lexeoff = self.lexstateeoff.get(state, None) + self.lexstate = state + + # ------------------------------------------------------------ + # push_state() - Changes the lexing state and saves old on stack + # ------------------------------------------------------------ + def push_state(self, state): + self.lexstatestack.append(self.lexstate) + self.begin(state) + + # ------------------------------------------------------------ + # pop_state() - Restores the previous state + # ------------------------------------------------------------ + def pop_state(self): + self.begin(self.lexstatestack.pop()) + + # ------------------------------------------------------------ + # current_state() - Returns the current lexing state + # ------------------------------------------------------------ + def current_state(self): + return self.lexstate + + # ------------------------------------------------------------ + # skip() - Skip ahead n characters + # ------------------------------------------------------------ + def skip(self, n): + self.lexpos += n + + # ------------------------------------------------------------ + # opttoken() - Return the next token from the Lexer + # + # Note: This function has been carefully implemented to be as fast + # as possible. Don't make changes unless you really know what + # you are doing + # ------------------------------------------------------------ + def token(self): + # Make local copies of frequently referenced attributes + lexpos = self.lexpos + lexlen = self.lexlen + lexignore = self.lexignore + lexdata = self.lexdata + + while lexpos < lexlen: + # This code provides some short-circuit code for whitespace, tabs, and other ignored characters + if lexdata[lexpos] in lexignore: + lexpos += 1 + continue + + # Look for a regular expression match + for lexre, lexindexfunc in self.lexre: + m = lexre.match(lexdata, lexpos) + if not m: + continue + + # Create a token for return + tok = LexToken() + tok.value = m.group() + tok.lineno = self.lineno + tok.lexpos = lexpos + + i = m.lastindex + func, tok.type = lexindexfunc[i] + + if not func: + # If no token type was set, it's an ignored token + if tok.type: + self.lexpos = m.end() + return tok + else: + lexpos = m.end() + break + + lexpos = m.end() + + # If token is processed by a function, call it + + tok.lexer = self # Set additional attributes useful in token rules + self.lexmatch = m + self.lexpos = lexpos + + newtok = func(tok) + + # Every function must return a token, if nothing, we just move to next token + if not newtok: + lexpos = self.lexpos # This is here in case user has updated lexpos. + lexignore = self.lexignore # This is here in case there was a state change + break + + # Verify type of the token. If not in the token map, raise an error + if not self.lexoptimize: + if newtok.type not in self.lextokens_all: + raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func.__code__.co_filename, func.__code__.co_firstlineno, + func.__name__, newtok.type), lexdata[lexpos:]) + + return newtok + else: + # No match, see if in literals + if lexdata[lexpos] in self.lexliterals: + tok = LexToken() + tok.value = lexdata[lexpos] + tok.lineno = self.lineno + tok.type = tok.value + tok.lexpos = lexpos + self.lexpos = lexpos + 1 + return tok + + # No match. Call t_error() if defined. + if self.lexerrorf: + tok = LexToken() + tok.value = self.lexdata[lexpos:] + tok.lineno = self.lineno + tok.type = 'error' + tok.lexer = self + tok.lexpos = lexpos + self.lexpos = lexpos + newtok = self.lexerrorf(tok) + if lexpos == self.lexpos: + # Error method didn't change text position at all. This is an error. + raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: + continue + return newtok + + self.lexpos = lexpos + raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos], lexpos), lexdata[lexpos:]) + + if self.lexeoff: + tok = LexToken() + tok.type = 'eof' + tok.value = '' + tok.lineno = self.lineno + tok.lexpos = lexpos + tok.lexer = self + self.lexpos = lexpos + newtok = self.lexeoff(tok) + return newtok + + self.lexpos = lexpos + 1 + if self.lexdata is None: + raise RuntimeError('No input string given with input()') + return None + + # Iterator interface + def __iter__(self): + return self + + def next(self): + t = self.token() + if t is None: + raise StopIteration + return t + + __next__ = next + +# ----------------------------------------------------------------------------- +# ==== Lex Builder === +# +# The functions and classes below are used to collect lexing information +# and build a Lexer object from it. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# _get_regex(func) +# +# Returns the regular expression assigned to a function either as a doc string +# or as a .regex attribute attached by the @TOKEN decorator. +# ----------------------------------------------------------------------------- +def _get_regex(func): + return getattr(func, 'regex', func.__doc__) + +# ----------------------------------------------------------------------------- +# get_caller_module_dict() +# +# This function returns a dictionary containing all of the symbols defined within +# a caller further down the call stack. This is used to get the environment +# associated with the yacc() call if none was provided. +# ----------------------------------------------------------------------------- +def get_caller_module_dict(levels): + f = sys._getframe(levels) + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + return ldict + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- +def _funcs_to_names(funclist, namelist): + result = [] + for f, name in zip(funclist, namelist): + if f and f[0]: + result.append((name, f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- +def _names_to_funcs(namelist, fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]], n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _form_master_re() +# +# This function takes a list of all of the regex components and attempts to +# form the master regular expression. Given limitations in the Python re +# module, it may be necessary to break the master regex into separate expressions. +# ----------------------------------------------------------------------------- +def _form_master_re(relist, reflags, ldict, toknames): + if not relist: + return [] + regex = '|'.join(relist) + try: + lexre = re.compile(regex, reflags) + + # Build the index to function map for the matching engine + lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1) + lexindexnames = lexindexfunc[:] + + for f, i in lexre.groupindex.items(): + handle = ldict.get(f, None) + if type(handle) in (types.FunctionType, types.MethodType): + lexindexfunc[i] = (handle, toknames[f]) + lexindexnames[i] = f + elif handle is not None: + lexindexnames[i] = f + if f.find('ignore_') > 0: + lexindexfunc[i] = (None, None) + else: + lexindexfunc[i] = (None, toknames[f]) + + return [(lexre, lexindexfunc)], [regex], [lexindexnames] + except Exception: + m = int(len(relist)/2) + if m == 0: + m = 1 + llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames) + rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames) + return (llist+rlist), (lre+rre), (lnames+rnames) + +# ----------------------------------------------------------------------------- +# def _statetoken(s,names) +# +# Given a declaration name s of the form "t_" and a dictionary whose keys are +# state names, this function returns a tuple (states,tokenname) where states +# is a tuple of state names and tokenname is the name of the token. For example, +# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') +# ----------------------------------------------------------------------------- +def _statetoken(s, names): + parts = s.split('_') + for i, part in enumerate(parts[1:], 1): + if part not in names and part != 'ANY': + break + + if i > 1: + states = tuple(parts[1:i]) + else: + states = ('INITIAL',) + + if 'ANY' in states: + states = tuple(names) + + tokenname = '_'.join(parts[i:]) + return (states, tokenname) + + +# ----------------------------------------------------------------------------- +# LexerReflect() +# +# This class represents information needed to build a lexer as extracted from a +# user's input file. +# ----------------------------------------------------------------------------- +class LexerReflect(object): + def __init__(self, ldict, log=None, reflags=0): + self.ldict = ldict + self.error_func = None + self.tokens = [] + self.reflags = reflags + self.stateinfo = {'INITIAL': 'inclusive'} + self.modules = set() + self.error = False + self.log = PlyLogger(sys.stderr) if log is None else log + + # Get all of the basic information + def get_all(self): + self.get_tokens() + self.get_literals() + self.get_states() + self.get_rules() + + # Validate all of the information + def validate_all(self): + self.validate_tokens() + self.validate_literals() + self.validate_rules() + return self.error + + # Get the tokens map + def get_tokens(self): + tokens = self.ldict.get('tokens', None) + if not tokens: + self.log.error('No token list is defined') + self.error = True + return + + if not isinstance(tokens, (list, tuple)): + self.log.error('tokens must be a list or tuple') + self.error = True + return + + if not tokens: + self.log.error('tokens is empty') + self.error = True + return + + self.tokens = tokens + + # Validate the tokens + def validate_tokens(self): + terminals = {} + for n in self.tokens: + if not _is_identifier.match(n): + self.log.error("Bad token name '%s'", n) + self.error = True + if n in terminals: + self.log.warning("Token '%s' multiply defined", n) + terminals[n] = 1 + + # Get the literals specifier + def get_literals(self): + self.literals = self.ldict.get('literals', '') + if not self.literals: + self.literals = '' + + # Validate literals + def validate_literals(self): + try: + for c in self.literals: + if not isinstance(c, StringTypes) or len(c) > 1: + self.log.error('Invalid literal %s. Must be a single character', repr(c)) + self.error = True + + except TypeError: + self.log.error('Invalid literals specification. literals must be a sequence of characters') + self.error = True + + def get_states(self): + self.states = self.ldict.get('states', None) + # Build statemap + if self.states: + if not isinstance(self.states, (tuple, list)): + self.log.error('states must be defined as a tuple or list') + self.error = True + else: + for s in self.states: + if not isinstance(s, tuple) or len(s) != 2: + self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')", repr(s)) + self.error = True + continue + name, statetype = s + if not isinstance(name, StringTypes): + self.log.error('State name %s must be a string', repr(name)) + self.error = True + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + self.log.error("State type for state %s must be 'inclusive' or 'exclusive'", name) + self.error = True + continue + if name in self.stateinfo: + self.log.error("State '%s' already defined", name) + self.error = True + continue + self.stateinfo[name] = statetype + + # Get all of the symbols with a t_ prefix and sort them into various + # categories (functions, strings, error functions, and ignore characters) + + def get_rules(self): + tsymbols = [f for f in self.ldict if f[:2] == 't_'] + + # Now build up a list of functions and a list of strings + self.toknames = {} # Mapping of symbols to token names + self.funcsym = {} # Symbols defined as functions + self.strsym = {} # Symbols defined as strings + self.ignore = {} # Ignore strings by state + self.errorf = {} # Error functions by state + self.eoff = {} # EOF functions by state + + for s in self.stateinfo: + self.funcsym[s] = [] + self.strsym[s] = [] + + if len(tsymbols) == 0: + self.log.error('No rules of the form t_rulename are defined') + self.error = True + return + + for f in tsymbols: + t = self.ldict[f] + states, tokname = _statetoken(f, self.stateinfo) + self.toknames[f] = tokname + + if hasattr(t, '__call__'): + if tokname == 'error': + for s in states: + self.errorf[s] = t + elif tokname == 'eof': + for s in states: + self.eoff[s] = t + elif tokname == 'ignore': + line = t.__code__.co_firstlineno + file = t.__code__.co_filename + self.log.error("%s:%d: Rule '%s' must be defined as a string", file, line, t.__name__) + self.error = True + else: + for s in states: + self.funcsym[s].append((f, t)) + elif isinstance(t, StringTypes): + if tokname == 'ignore': + for s in states: + self.ignore[s] = t + if '\\' in t: + self.log.warning("%s contains a literal backslash '\\'", f) + + elif tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", f) + self.error = True + else: + for s in states: + self.strsym[s].append((f, t)) + else: + self.log.error('%s not defined as a function or string', f) + self.error = True + + # Sort the functions by line number + for f in self.funcsym.values(): + f.sort(key=lambda x: x[1].__code__.co_firstlineno) + + # Sort the strings by regular expression length + for s in self.strsym.values(): + s.sort(key=lambda x: len(x[1]), reverse=True) + + # Validate all of the t_rules collected + def validate_rules(self): + for state in self.stateinfo: + # Validate all rules defined by functions + + for fname, f in self.funcsym[state]: + line = f.__code__.co_firstlineno + file = f.__code__.co_filename + module = inspect.getmodule(f) + self.modules.add(module) + + tokname = self.toknames[fname] + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = f.__code__.co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments", file, line, f.__name__) + self.error = True + continue + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file, line, f.__name__) + self.error = True + continue + + if not _get_regex(f): + self.log.error("%s:%d: No regular expression defined for rule '%s'", file, line, f.__name__) + self.error = True + continue + + try: + c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags) + if c.match(''): + self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file, line, f.__name__) + self.error = True + except re.error as e: + self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e) + if '#' in _get_regex(f): + self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'", file, line, f.__name__) + self.error = True + + # Validate all rules defined by strings + for name, r in self.strsym[state]: + tokname = self.toknames[name] + if tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", name) + self.error = True + continue + + if tokname not in self.tokens and tokname.find('ignore_') < 0: + self.log.error("Rule '%s' defined for an unspecified token %s", name, tokname) + self.error = True + continue + + try: + c = re.compile('(?P<%s>%s)' % (name, r), self.reflags) + if (c.match('')): + self.log.error("Regular expression for rule '%s' matches empty string", name) + self.error = True + except re.error as e: + self.log.error("Invalid regular expression for rule '%s'. %s", name, e) + if '#' in r: + self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'", name) + self.error = True + + if not self.funcsym[state] and not self.strsym[state]: + self.log.error("No rules defined for state '%s'", state) + self.error = True + + # Validate the error function + efunc = self.errorf.get(state, None) + if efunc: + f = efunc + line = f.__code__.co_firstlineno + file = f.__code__.co_filename + module = inspect.getmodule(f) + self.modules.add(module) + + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = f.__code__.co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments", file, line, f.__name__) + self.error = True + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file, line, f.__name__) + self.error = True + + for module in self.modules: + self.validate_module(module) + + # ----------------------------------------------------------------------------- + # validate_module() + # + # This checks to see if there are duplicated t_rulename() functions or strings + # in the parser input file. This is done using a simple regular expression + # match on each line in the source code of the given module. + # ----------------------------------------------------------------------------- + + def validate_module(self, module): + try: + lines, linen = inspect.getsourcelines(module) + except IOError: + return + + fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') + sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') + + counthash = {} + linen += 1 + for line in lines: + m = fre.match(line) + if not m: + m = sre.match(line) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + filename = inspect.getsourcefile(module) + self.log.error('%s:%d: Rule %s redefined. Previously defined on line %d', filename, linen, name, prev) + self.error = True + linen += 1 + +# ----------------------------------------------------------------------------- +# lex(module) +# +# Build all of the regular expression rules from definitions in the supplied module +# ----------------------------------------------------------------------------- +def lex(module=None, object=None, debug=False, optimize=False, lextab='lextab', + reflags=int(re.VERBOSE), nowarn=False, outputdir=None, debuglog=None, errorlog=None): + + if lextab is None: + lextab = 'lextab' + + global lexer + + ldict = None + stateinfo = {'INITIAL': 'inclusive'} + lexobj = Lexer() + lexobj.lexoptimize = optimize + global token, input + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + if debug: + if debuglog is None: + debuglog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the lexer + if object: + module = object + + # Get the module dictionary used for the parser + if module: + _items = [(k, getattr(module, k)) for k in dir(module)] + ldict = dict(_items) + # If no __file__ attribute is available, try to obtain it from the __module__ instead + if '__file__' not in ldict: + ldict['__file__'] = sys.modules[ldict['__module__']].__file__ + else: + ldict = get_caller_module_dict(2) + + # Determine if the module is package of a package or not. + # If so, fix the tabmodule setting so that tables load correctly + pkg = ldict.get('__package__') + if pkg and isinstance(lextab, str): + if '.' not in lextab: + lextab = pkg + '.' + lextab + + # Collect parser information from the dictionary + linfo = LexerReflect(ldict, log=errorlog, reflags=reflags) + linfo.get_all() + if not optimize: + if linfo.validate_all(): + raise SyntaxError("Can't build lexer") + + if optimize and lextab: + try: + lexobj.readtab(lextab, ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + + except ImportError: + pass + + # Dump some basic debugging information + if debug: + debuglog.info('lex: tokens = %r', linfo.tokens) + debuglog.info('lex: literals = %r', linfo.literals) + debuglog.info('lex: states = %r', linfo.stateinfo) + + # Build a dictionary of valid token names + lexobj.lextokens = set() + for n in linfo.tokens: + lexobj.lextokens.add(n) + + # Get literals specification + if isinstance(linfo.literals, (list, tuple)): + lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) + else: + lexobj.lexliterals = linfo.literals + + lexobj.lextokens_all = lexobj.lextokens | set(lexobj.lexliterals) + + # Get the stateinfo dictionary + stateinfo = linfo.stateinfo + + regexs = {} + # Build the master regular expressions + for state in stateinfo: + regex_list = [] + + # Add rules defined by functions first + for fname, f in linfo.funcsym[state]: + regex_list.append('(?P<%s>%s)' % (fname, _get_regex(f))) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", fname, _get_regex(f), state) + + # Now add all of the simple rules + for name, r in linfo.strsym[state]: + regex_list.append('(?P<%s>%s)' % (name, r)) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", name, r, state) + + regexs[state] = regex_list + + # Build the master regular expressions + + if debug: + debuglog.info('lex: ==== MASTER REGEXS FOLLOW ====') + + for state in regexs: + lexre, re_text, re_names = _form_master_re(regexs[state], reflags, ldict, linfo.toknames) + lexobj.lexstatere[state] = lexre + lexobj.lexstateretext[state] = re_text + lexobj.lexstaterenames[state] = re_names + if debug: + for i, text in enumerate(re_text): + debuglog.info("lex: state '%s' : regex[%d] = '%s'", state, i, text) + + # For inclusive states, we need to add the regular expressions from the INITIAL state + for state, stype in stateinfo.items(): + if state != 'INITIAL' and stype == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) + + lexobj.lexstateinfo = stateinfo + lexobj.lexre = lexobj.lexstatere['INITIAL'] + lexobj.lexretext = lexobj.lexstateretext['INITIAL'] + lexobj.lexreflags = reflags + + # Set up ignore variables + lexobj.lexstateignore = linfo.ignore + lexobj.lexignore = lexobj.lexstateignore.get('INITIAL', '') + + # Set up error functions + lexobj.lexstateerrorf = linfo.errorf + lexobj.lexerrorf = linfo.errorf.get('INITIAL', None) + if not lexobj.lexerrorf: + errorlog.warning('No t_error rule is defined') + + # Set up eof functions + lexobj.lexstateeoff = linfo.eoff + lexobj.lexeoff = linfo.eoff.get('INITIAL', None) + + # Check state information for ignore and error rules + for s, stype in stateinfo.items(): + if stype == 'exclusive': + if s not in linfo.errorf: + errorlog.warning("No error rule is defined for exclusive state '%s'", s) + if s not in linfo.ignore and lexobj.lexignore: + errorlog.warning("No ignore rule is defined for exclusive state '%s'", s) + elif stype == 'inclusive': + if s not in linfo.errorf: + linfo.errorf[s] = linfo.errorf.get('INITIAL', None) + if s not in linfo.ignore: + linfo.ignore[s] = linfo.ignore.get('INITIAL', '') + + # Create global versions of the token() and input() functions + token = lexobj.token + input = lexobj.input + lexer = lexobj + + # If in optimize mode, we write the lextab + if lextab and optimize: + if outputdir is None: + # If no output directory is set, the location of the output files + # is determined according to the following rules: + # - If lextab specifies a package, files go into that package directory + # - Otherwise, files go in the same directory as the specifying module + if isinstance(lextab, types.ModuleType): + srcfile = lextab.__file__ + else: + if '.' not in lextab: + srcfile = ldict['__file__'] + else: + parts = lextab.split('.') + pkgname = '.'.join(parts[:-1]) + exec('import %s' % pkgname) + srcfile = getattr(sys.modules[pkgname], '__file__', '') + outputdir = os.path.dirname(srcfile) + try: + lexobj.writetab(lextab, outputdir) + if lextab in sys.modules: + del sys.modules[lextab] + except IOError as e: + errorlog.warning("Couldn't write lextab module %r. %s" % (lextab, e)) + + return lexobj + +# ----------------------------------------------------------------------------- +# runmain() +# +# This runs the lexer as a main program +# ----------------------------------------------------------------------------- + +def runmain(lexer=None, data=None): + if not data: + try: + filename = sys.argv[1] + f = open(filename) + data = f.read() + f.close() + except IndexError: + sys.stdout.write('Reading from standard input (type EOF to end):\n') + data = sys.stdin.read() + + if lexer: + _input = lexer.input + else: + _input = input + _input(data) + if lexer: + _token = lexer.token + else: + _token = token + + while True: + tok = _token() + if not tok: + break + sys.stdout.write('(%s,%r,%d,%d)\n' % (tok.type, tok.value, tok.lineno, tok.lexpos)) + +# ----------------------------------------------------------------------------- +# @TOKEN(regex) +# +# This decorator function can be used to set the regex expression on a function +# when its docstring might need to be set in an alternative way +# ----------------------------------------------------------------------------- + +def TOKEN(r): + def set_regex(f): + if hasattr(r, '__call__'): + f.regex = _get_regex(r) + else: + f.regex = r + return f + return set_regex + +# Alternative spelling of the TOKEN decorator +Token = TOKEN diff --git a/ddtrace/vendor/ply/yacc.py b/ddtrace/vendor/ply/yacc.py new file mode 100644 index 00000000000..88188a1e8ea --- /dev/null +++ b/ddtrace/vendor/ply/yacc.py @@ -0,0 +1,3502 @@ +# ----------------------------------------------------------------------------- +# ply: yacc.py +# +# Copyright (C) 2001-2018 +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# +# This implements an LR parser that is constructed from grammar rules defined +# as Python functions. The grammar is specified by supplying the BNF inside +# Python documentation strings. The inspiration for this technique was borrowed +# from John Aycock's Spark parsing system. PLY might be viewed as cross between +# Spark and the GNU bison utility. +# +# The current implementation is only somewhat object-oriented. The +# LR parser itself is defined in terms of an object (which allows multiple +# parsers to co-exist). However, most of the variables used during table +# construction are defined in terms of global variables. Users shouldn't +# notice unless they are trying to define multiple parsers at the same +# time using threads (in which case they should have their head examined). +# +# This implementation supports both SLR and LALR(1) parsing. LALR(1) +# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), +# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, +# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced +# by the more efficient DeRemer and Pennello algorithm. +# +# :::::::: WARNING ::::::: +# +# Construction of LR parsing tables is fairly complicated and expensive. +# To make this module run fast, a *LOT* of work has been put into +# optimization---often at the expensive of readability and what might +# consider to be good Python "coding style." Modify the code at your +# own risk! +# ---------------------------------------------------------------------------- + +import re +import types +import sys +import os.path +import inspect +import warnings + +__version__ = '3.11' +__tabversion__ = '3.10' + +#----------------------------------------------------------------------------- +# === User configurable parameters === +# +# Change these to modify the default behavior of yacc (if you wish) +#----------------------------------------------------------------------------- + +yaccdebug = True # Debugging mode. If set, yacc generates a + # a 'parser.out' file in the current directory + +debug_file = 'parser.out' # Default name of the debugging file +tab_module = 'parsetab' # Default name of the table module +default_lr = 'LALR' # Default LR table generation method + +error_count = 3 # Number of symbols that must be shifted to leave recovery mode + +yaccdevel = False # Set to True if developing yacc. This turns off optimized + # implementations of certain functions. + +resultlimit = 40 # Size limit of results when running in debug mode. + +pickle_protocol = 0 # Protocol to use when writing pickle files + +# String type-checking compatibility +if sys.version_info[0] < 3: + string_types = basestring +else: + string_types = str + +MAXINT = sys.maxsize + +# This object is a stand-in for a logging object created by the +# logging module. PLY will use this by default to create things +# such as the parser.out file. If a user wants more detailed +# information, they can create their own logging object and pass +# it into PLY. + +class PlyLogger(object): + def __init__(self, f): + self.f = f + + def debug(self, msg, *args, **kwargs): + self.f.write((msg % args) + '\n') + + info = debug + + def warning(self, msg, *args, **kwargs): + self.f.write('WARNING: ' + (msg % args) + '\n') + + def error(self, msg, *args, **kwargs): + self.f.write('ERROR: ' + (msg % args) + '\n') + + critical = debug + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self, name): + return self + + def __call__(self, *args, **kwargs): + return self + +# Exception raised for yacc-related errors +class YaccError(Exception): + pass + +# Format the result message that the parser produces when running in debug mode. +def format_result(r): + repr_str = repr(r) + if '\n' in repr_str: + repr_str = repr(repr_str) + if len(repr_str) > resultlimit: + repr_str = repr_str[:resultlimit] + ' ...' + result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str) + return result + +# Format stack entries when the parser is running in debug mode +def format_stack_entry(r): + repr_str = repr(r) + if '\n' in repr_str: + repr_str = repr(repr_str) + if len(repr_str) < 16: + return repr_str + else: + return '<%s @ 0x%x>' % (type(r).__name__, id(r)) + +# Panic mode error recovery support. This feature is being reworked--much of the +# code here is to offer a deprecation/backwards compatible transition + +_errok = None +_token = None +_restart = None +_warnmsg = '''PLY: Don't use global functions errok(), token(), and restart() in p_error(). +Instead, invoke the methods on the associated parser instance: + + def p_error(p): + ... + # Use parser.errok(), parser.token(), parser.restart() + ... + + parser = yacc.yacc() +''' + +def errok(): + warnings.warn(_warnmsg) + return _errok() + +def restart(): + warnings.warn(_warnmsg) + return _restart() + +def token(): + warnings.warn(_warnmsg) + return _token() + +# Utility function to call the p_error() function with some deprecation hacks +def call_errorfunc(errorfunc, token, parser): + global _errok, _token, _restart + _errok = parser.errok + _token = parser.token + _restart = parser.restart + r = errorfunc(token) + try: + del _errok, _token, _restart + except NameError: + pass + return r + +#----------------------------------------------------------------------------- +# === LR Parsing Engine === +# +# The following classes are used for the LR parser itself. These are not +# used during table construction and are independent of the actual LR +# table generation algorithm +#----------------------------------------------------------------------------- + +# This class is used to hold non-terminal grammar symbols during parsing. +# It normally has the following attributes set: +# .type = Grammar symbol type +# .value = Symbol value +# .lineno = Starting line number +# .endlineno = Ending line number (optional, set automatically) +# .lexpos = Starting lex position +# .endlexpos = Ending lex position (optional, set automatically) + +class YaccSymbol: + def __str__(self): + return self.type + + def __repr__(self): + return str(self) + +# This class is a wrapper around the objects actually passed to each +# grammar rule. Index lookup and assignment actually assign the +# .value attribute of the underlying YaccSymbol object. +# The lineno() method returns the line number of a given +# item (or 0 if not defined). The linespan() method returns +# a tuple of (startline,endline) representing the range of lines +# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) +# representing the range of positional information for a symbol. + +class YaccProduction: + def __init__(self, s, stack=None): + self.slice = s + self.stack = stack + self.lexer = None + self.parser = None + + def __getitem__(self, n): + if isinstance(n, slice): + return [s.value for s in self.slice[n]] + elif n >= 0: + return self.slice[n].value + else: + return self.stack[n].value + + def __setitem__(self, n, v): + self.slice[n].value = v + + def __getslice__(self, i, j): + return [s.value for s in self.slice[i:j]] + + def __len__(self): + return len(self.slice) + + def lineno(self, n): + return getattr(self.slice[n], 'lineno', 0) + + def set_lineno(self, n, lineno): + self.slice[n].lineno = lineno + + def linespan(self, n): + startline = getattr(self.slice[n], 'lineno', 0) + endline = getattr(self.slice[n], 'endlineno', startline) + return startline, endline + + def lexpos(self, n): + return getattr(self.slice[n], 'lexpos', 0) + + def set_lexpos(self, n, lexpos): + self.slice[n].lexpos = lexpos + + def lexspan(self, n): + startpos = getattr(self.slice[n], 'lexpos', 0) + endpos = getattr(self.slice[n], 'endlexpos', startpos) + return startpos, endpos + + def error(self): + raise SyntaxError + +# ----------------------------------------------------------------------------- +# == LRParser == +# +# The LR Parsing engine. +# ----------------------------------------------------------------------------- + +class LRParser: + def __init__(self, lrtab, errorf): + self.productions = lrtab.lr_productions + self.action = lrtab.lr_action + self.goto = lrtab.lr_goto + self.errorfunc = errorf + self.set_defaulted_states() + self.errorok = True + + def errok(self): + self.errorok = True + + def restart(self): + del self.statestack[:] + del self.symstack[:] + sym = YaccSymbol() + sym.type = '$end' + self.symstack.append(sym) + self.statestack.append(0) + + # Defaulted state support. + # This method identifies parser states where there is only one possible reduction action. + # For such states, the parser can make a choose to make a rule reduction without consuming + # the next look-ahead token. This delayed invocation of the tokenizer can be useful in + # certain kinds of advanced parsing situations where the lexer and parser interact with + # each other or change states (i.e., manipulation of scope, lexer states, etc.). + # + # See: http://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions + def set_defaulted_states(self): + self.defaulted_states = {} + for state, actions in self.action.items(): + rules = list(actions.values()) + if len(rules) == 1 and rules[0] < 0: + self.defaulted_states[state] = rules[0] + + def disable_defaulted_states(self): + self.defaulted_states = {} + + def parse(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + if debug or yaccdevel: + if isinstance(debug, int): + debug = PlyLogger(sys.stderr) + return self.parsedebug(input, lexer, debug, tracking, tokenfunc) + elif tracking: + return self.parseopt(input, lexer, debug, tracking, tokenfunc) + else: + return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc) + + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parsedebug(). + # + # This is the debugging enabled version of parse(). All changes made to the + # parsing engine should be made here. Optimized versions of this function + # are automatically created by the ply/ygen.py script. This script cuts out + # sections enclosed in markers such as this: + # + # #--! DEBUG + # statements + # #--! DEBUG + # + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parsedebug(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parsedebug-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + #--! DEBUG + debug.info('PLY: PARSE DEBUG START') + #--! DEBUG + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + #--! DEBUG + debug.debug('') + debug.debug('State : %s', state) + #--! DEBUG + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + #--! DEBUG + debug.debug('Defaulted state %s: Reduce using %d', state, -t) + #--! DEBUG + + #--! DEBUG + debug.debug('Stack : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + #--! DEBUG + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + #--! DEBUG + debug.debug('Action : Shift and goto state %s', t) + #--! DEBUG + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + #--! DEBUG + if plen: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, + '['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']', + goto[statestack[-1-plen]][pname]) + else: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [], + goto[statestack[-1]][pname]) + + #--! DEBUG + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + #--! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1, 'endlineno', t1.lineno) + sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) + #--! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + #--! DEBUG + debug.info('Result : %s', format_result(pslice[0])) + #--! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + #--! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + #--! TRACKING + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + #--! DEBUG + debug.info('Result : %s', format_result(pslice[0])) + #--! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + #--! DEBUG + debug.info('Done : Returning %s', format_result(result)) + debug.info('PLY: PARSE DEBUG END') + #--! DEBUG + return result + + if t is None: + + #--! DEBUG + debug.error('Error : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + #--! DEBUG + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + #--! TRACKING + if tracking: + sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) + sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) + #--! TRACKING + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + #--! TRACKING + if tracking: + lookahead.lineno = sym.lineno + lookahead.lexpos = sym.lexpos + #--! TRACKING + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parsedebug-end + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt(). + # + # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY! + # This code is automatically generated by the ply/ygen.py script. Make + # changes to the parsedebug() method instead. + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parseopt(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parseopt-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + #--! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1, 'endlineno', t1.lineno) + sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) + #--! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + #--! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + #--! TRACKING + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + return result + + if t is None: + + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + #--! TRACKING + if tracking: + sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) + sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) + #--! TRACKING + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + #--! TRACKING + if tracking: + lookahead.lineno = sym.lineno + lookahead.lexpos = sym.lexpos + #--! TRACKING + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parseopt-end + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt_notrack(). + # + # Optimized version of parseopt() with line number tracking removed. + # DO NOT EDIT THIS CODE DIRECTLY. This code is automatically generated + # by the ply/ygen.py script. Make changes to the parsedebug() method instead. + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parseopt_notrack(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parseopt-notrack-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + return result + + if t is None: + + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parseopt-notrack-end + +# ----------------------------------------------------------------------------- +# === Grammar Representation === +# +# The following functions, classes, and variables are used to represent and +# manipulate the rules that make up a grammar. +# ----------------------------------------------------------------------------- + +# regex matching identifiers +_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') + +# ----------------------------------------------------------------------------- +# class Production: +# +# This class stores the raw information about a single production or grammar rule. +# A grammar rule refers to a specification such as this: +# +# expr : expr PLUS term +# +# Here are the basic attributes defined on all productions +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','PLUS','term'] +# prec - Production precedence level +# number - Production number. +# func - Function that executes on reduce +# file - File where production function is defined +# lineno - Line number where production function is defined +# +# The following attributes are defined or optional. +# +# len - Length of the production (number of symbols on right hand side) +# usyms - Set of unique symbols found in the production +# ----------------------------------------------------------------------------- + +class Production(object): + reduced = 0 + def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0): + self.name = name + self.prod = tuple(prod) + self.number = number + self.func = func + self.callable = None + self.file = file + self.line = line + self.prec = precedence + + # Internal settings used during table construction + + self.len = len(self.prod) # Length of the production + + # Create a list of unique production symbols used in the production + self.usyms = [] + for s in self.prod: + if s not in self.usyms: + self.usyms.append(s) + + # List of all LR items for the production + self.lr_items = [] + self.lr_next = None + + # Create a string representation + if self.prod: + self.str = '%s -> %s' % (self.name, ' '.join(self.prod)) + else: + self.str = '%s -> ' % self.name + + def __str__(self): + return self.str + + def __repr__(self): + return 'Production(' + str(self) + ')' + + def __len__(self): + return len(self.prod) + + def __nonzero__(self): + return 1 + + def __getitem__(self, index): + return self.prod[index] + + # Return the nth lr_item from the production (or None if at the end) + def lr_item(self, n): + if n > len(self.prod): + return None + p = LRItem(self, n) + # Precompute the list of productions immediately following. + try: + p.lr_after = self.Prodnames[p.prod[n+1]] + except (IndexError, KeyError): + p.lr_after = [] + try: + p.lr_before = p.prod[n-1] + except IndexError: + p.lr_before = None + return p + + # Bind the production function name to a callable + def bind(self, pdict): + if self.func: + self.callable = pdict[self.func] + +# This class serves as a minimal standin for Production objects when +# reading table data from files. It only contains information +# actually used by the LR parsing engine, plus some additional +# debugging information. +class MiniProduction(object): + def __init__(self, str, name, len, func, file, line): + self.name = name + self.len = len + self.func = func + self.callable = None + self.file = file + self.line = line + self.str = str + + def __str__(self): + return self.str + + def __repr__(self): + return 'MiniProduction(%s)' % self.str + + # Bind the production function name to a callable + def bind(self, pdict): + if self.func: + self.callable = pdict[self.func] + + +# ----------------------------------------------------------------------------- +# class LRItem +# +# This class represents a specific stage of parsing a production rule. For +# example: +# +# expr : expr . PLUS term +# +# In the above, the "." represents the current location of the parse. Here +# basic attributes: +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] +# number - Production number. +# +# lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' +# then lr_next refers to 'expr -> expr PLUS . term' +# lr_index - LR item index (location of the ".") in the prod list. +# lookaheads - LALR lookahead symbols for this item +# len - Length of the production (number of symbols on right hand side) +# lr_after - List of all productions that immediately follow +# lr_before - Grammar symbol immediately before +# ----------------------------------------------------------------------------- + +class LRItem(object): + def __init__(self, p, n): + self.name = p.name + self.prod = list(p.prod) + self.number = p.number + self.lr_index = n + self.lookaheads = {} + self.prod.insert(n, '.') + self.prod = tuple(self.prod) + self.len = len(self.prod) + self.usyms = p.usyms + + def __str__(self): + if self.prod: + s = '%s -> %s' % (self.name, ' '.join(self.prod)) + else: + s = '%s -> ' % self.name + return s + + def __repr__(self): + return 'LRItem(' + str(self) + ')' + +# ----------------------------------------------------------------------------- +# rightmost_terminal() +# +# Return the rightmost terminal from a list of symbols. Used in add_production() +# ----------------------------------------------------------------------------- +def rightmost_terminal(symbols, terminals): + i = len(symbols) - 1 + while i >= 0: + if symbols[i] in terminals: + return symbols[i] + i -= 1 + return None + +# ----------------------------------------------------------------------------- +# === GRAMMAR CLASS === +# +# The following class represents the contents of the specified grammar along +# with various computed properties such as first sets, follow sets, LR items, etc. +# This data is used for critical parts of the table generation process later. +# ----------------------------------------------------------------------------- + +class GrammarError(YaccError): + pass + +class Grammar(object): + def __init__(self, terminals): + self.Productions = [None] # A list of all of the productions. The first + # entry is always reserved for the purpose of + # building an augmented grammar + + self.Prodnames = {} # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. + + self.Prodmap = {} # A dictionary that is only used to detect duplicate + # productions. + + self.Terminals = {} # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. + + for term in terminals: + self.Terminals[term] = [] + + self.Terminals['error'] = [] + + self.Nonterminals = {} # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. + + self.First = {} # A dictionary of precomputed FIRST(x) symbols + + self.Follow = {} # A dictionary of precomputed FOLLOW(x) symbols + + self.Precedence = {} # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) + + self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer. + # This is only used to provide error checking and to generate + # a warning about unused precedence rules. + + self.Start = None # Starting symbol for the grammar + + + def __len__(self): + return len(self.Productions) + + def __getitem__(self, index): + return self.Productions[index] + + # ----------------------------------------------------------------------------- + # set_precedence() + # + # Sets the precedence for a given terminal. assoc is the associativity such as + # 'left','right', or 'nonassoc'. level is a numeric level. + # + # ----------------------------------------------------------------------------- + + def set_precedence(self, term, assoc, level): + assert self.Productions == [None], 'Must call set_precedence() before add_production()' + if term in self.Precedence: + raise GrammarError('Precedence already specified for terminal %r' % term) + if assoc not in ['left', 'right', 'nonassoc']: + raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") + self.Precedence[term] = (assoc, level) + + # ----------------------------------------------------------------------------- + # add_production() + # + # Given an action function, this function assembles a production rule and + # computes its precedence level. + # + # The production rule is supplied as a list of symbols. For example, + # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and + # symbols ['expr','PLUS','term']. + # + # Precedence is determined by the precedence of the right-most non-terminal + # or the precedence of a terminal specified by %prec. + # + # A variety of error checks are performed to make sure production symbols + # are valid and that %prec is used correctly. + # ----------------------------------------------------------------------------- + + def add_production(self, prodname, syms, func=None, file='', line=0): + + if prodname in self.Terminals: + raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname)) + if prodname == 'error': + raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname)) + if not _is_identifier.match(prodname): + raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname)) + + # Look for literal tokens + for n, s in enumerate(syms): + if s[0] in "'\"": + try: + c = eval(s) + if (len(c) > 1): + raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' % + (file, line, s, prodname)) + if c not in self.Terminals: + self.Terminals[c] = [] + syms[n] = c + continue + except SyntaxError: + pass + if not _is_identifier.match(s) and s != '%prec': + raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname)) + + # Determine the precedence level + if '%prec' in syms: + if syms[-1] == '%prec': + raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line)) + if syms[-2] != '%prec': + raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' % + (file, line)) + precname = syms[-1] + prodprec = self.Precedence.get(precname) + if not prodprec: + raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname)) + else: + self.UsedPrecedence.add(precname) + del syms[-2:] # Drop %prec from the rule + else: + # If no %prec, precedence is determined by the rightmost terminal symbol + precname = rightmost_terminal(syms, self.Terminals) + prodprec = self.Precedence.get(precname, ('right', 0)) + + # See if the rule is already in the rulemap + map = '%s -> %s' % (prodname, syms) + if map in self.Prodmap: + m = self.Prodmap[map] + raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) + + 'Previous definition at %s:%d' % (m.file, m.line)) + + # From this point on, everything is valid. Create a new Production instance + pnumber = len(self.Productions) + if prodname not in self.Nonterminals: + self.Nonterminals[prodname] = [] + + # Add the production number to Terminals and Nonterminals + for t in syms: + if t in self.Terminals: + self.Terminals[t].append(pnumber) + else: + if t not in self.Nonterminals: + self.Nonterminals[t] = [] + self.Nonterminals[t].append(pnumber) + + # Create a production and add it to the list of productions + p = Production(pnumber, prodname, syms, prodprec, func, file, line) + self.Productions.append(p) + self.Prodmap[map] = p + + # Add to the global productions list + try: + self.Prodnames[prodname].append(p) + except KeyError: + self.Prodnames[prodname] = [p] + + # ----------------------------------------------------------------------------- + # set_start() + # + # Sets the starting symbol and creates the augmented grammar. Production + # rule 0 is S' -> start where start is the start symbol. + # ----------------------------------------------------------------------------- + + def set_start(self, start=None): + if not start: + start = self.Productions[1].name + if start not in self.Nonterminals: + raise GrammarError('start symbol %s undefined' % start) + self.Productions[0] = Production(0, "S'", [start]) + self.Nonterminals[start].append(0) + self.Start = start + + # ----------------------------------------------------------------------------- + # find_unreachable() + # + # Find all of the nonterminal symbols that can't be reached from the starting + # symbol. Returns a list of nonterminals that can't be reached. + # ----------------------------------------------------------------------------- + + def find_unreachable(self): + + # Mark all symbols that are reachable from a symbol s + def mark_reachable_from(s): + if s in reachable: + return + reachable.add(s) + for p in self.Prodnames.get(s, []): + for r in p.prod: + mark_reachable_from(r) + + reachable = set() + mark_reachable_from(self.Productions[0].prod[0]) + return [s for s in self.Nonterminals if s not in reachable] + + # ----------------------------------------------------------------------------- + # infinite_cycles() + # + # This function looks at the various parsing rules and tries to detect + # infinite recursion cycles (grammar rules where there is no possible way + # to derive a string of only terminals). + # ----------------------------------------------------------------------------- + + def infinite_cycles(self): + terminates = {} + + # Terminals: + for t in self.Terminals: + terminates[t] = True + + terminates['$end'] = True + + # Nonterminals: + + # Initialize to false: + for n in self.Nonterminals: + terminates[n] = False + + # Then propagate termination until no change: + while True: + some_change = False + for (n, pl) in self.Prodnames.items(): + # Nonterminal n terminates iff any of its productions terminates. + for p in pl: + # Production p terminates iff all of its rhs symbols terminate. + for s in p.prod: + if not terminates[s]: + # The symbol s does not terminate, + # so production p does not terminate. + p_terminates = False + break + else: + # didn't break from the loop, + # so every symbol s terminates + # so production p terminates. + p_terminates = True + + if p_terminates: + # symbol n terminates! + if not terminates[n]: + terminates[n] = True + some_change = True + # Don't need to consider any more productions for this n. + break + + if not some_change: + break + + infinite = [] + for (s, term) in terminates.items(): + if not term: + if s not in self.Prodnames and s not in self.Terminals and s != 'error': + # s is used-but-not-defined, and we've already warned of that, + # so it would be overkill to say that it's also non-terminating. + pass + else: + infinite.append(s) + + return infinite + + # ----------------------------------------------------------------------------- + # undefined_symbols() + # + # Find all symbols that were used the grammar, but not defined as tokens or + # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol + # and prod is the production where the symbol was used. + # ----------------------------------------------------------------------------- + def undefined_symbols(self): + result = [] + for p in self.Productions: + if not p: + continue + + for s in p.prod: + if s not in self.Prodnames and s not in self.Terminals and s != 'error': + result.append((s, p)) + return result + + # ----------------------------------------------------------------------------- + # unused_terminals() + # + # Find all terminals that were defined, but not used by the grammar. Returns + # a list of all symbols. + # ----------------------------------------------------------------------------- + def unused_terminals(self): + unused_tok = [] + for s, v in self.Terminals.items(): + if s != 'error' and not v: + unused_tok.append(s) + + return unused_tok + + # ------------------------------------------------------------------------------ + # unused_rules() + # + # Find all grammar rules that were defined, but not used (maybe not reachable) + # Returns a list of productions. + # ------------------------------------------------------------------------------ + + def unused_rules(self): + unused_prod = [] + for s, v in self.Nonterminals.items(): + if not v: + p = self.Prodnames[s][0] + unused_prod.append(p) + return unused_prod + + # ----------------------------------------------------------------------------- + # unused_precedence() + # + # Returns a list of tuples (term,precedence) corresponding to precedence + # rules that were never used by the grammar. term is the name of the terminal + # on which precedence was applied and precedence is a string such as 'left' or + # 'right' corresponding to the type of precedence. + # ----------------------------------------------------------------------------- + + def unused_precedence(self): + unused = [] + for termname in self.Precedence: + if not (termname in self.Terminals or termname in self.UsedPrecedence): + unused.append((termname, self.Precedence[termname][0])) + + return unused + + # ------------------------------------------------------------------------- + # _first() + # + # Compute the value of FIRST1(beta) where beta is a tuple of symbols. + # + # During execution of compute_first1, the result may be incomplete. + # Afterward (e.g., when called from compute_follow()), it will be complete. + # ------------------------------------------------------------------------- + def _first(self, beta): + + # We are computing First(x1,x2,x3,...,xn) + result = [] + for x in beta: + x_produces_empty = False + + # Add all the non- symbols of First[x] to the result. + for f in self.First[x]: + if f == '': + x_produces_empty = True + else: + if f not in result: + result.append(f) + + if x_produces_empty: + # We have to consider the next x in beta, + # i.e. stay in the loop. + pass + else: + # We don't have to consider any further symbols in beta. + break + else: + # There was no 'break' from the loop, + # so x_produces_empty was true for all x in beta, + # so beta produces empty as well. + result.append('') + + return result + + # ------------------------------------------------------------------------- + # compute_first() + # + # Compute the value of FIRST1(X) for all symbols + # ------------------------------------------------------------------------- + def compute_first(self): + if self.First: + return self.First + + # Terminals: + for t in self.Terminals: + self.First[t] = [t] + + self.First['$end'] = ['$end'] + + # Nonterminals: + + # Initialize to the empty set: + for n in self.Nonterminals: + self.First[n] = [] + + # Then propagate symbols until no change: + while True: + some_change = False + for n in self.Nonterminals: + for p in self.Prodnames[n]: + for f in self._first(p.prod): + if f not in self.First[n]: + self.First[n].append(f) + some_change = True + if not some_change: + break + + return self.First + + # --------------------------------------------------------------------- + # compute_follow() + # + # Computes all of the follow sets for every non-terminal symbol. The + # follow set is the set of all symbols that might follow a given + # non-terminal. See the Dragon book, 2nd Ed. p. 189. + # --------------------------------------------------------------------- + def compute_follow(self, start=None): + # If already computed, return the result + if self.Follow: + return self.Follow + + # If first sets not computed yet, do that first. + if not self.First: + self.compute_first() + + # Add '$end' to the follow list of the start symbol + for k in self.Nonterminals: + self.Follow[k] = [] + + if not start: + start = self.Productions[1].name + + self.Follow[start] = ['$end'] + + while True: + didadd = False + for p in self.Productions[1:]: + # Here is the production set + for i, B in enumerate(p.prod): + if B in self.Nonterminals: + # Okay. We got a non-terminal in a production + fst = self._first(p.prod[i+1:]) + hasempty = False + for f in fst: + if f != '' and f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = True + if f == '': + hasempty = True + if hasempty or i == (len(p.prod)-1): + # Add elements of follow(a) to follow(b) + for f in self.Follow[p.name]: + if f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = True + if not didadd: + break + return self.Follow + + + # ----------------------------------------------------------------------------- + # build_lritems() + # + # This function walks the list of productions and builds a complete set of the + # LR items. The LR items are stored in two ways: First, they are uniquely + # numbered and placed in the list _lritems. Second, a linked list of LR items + # is built for each production. For example: + # + # E -> E PLUS E + # + # Creates the list + # + # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] + # ----------------------------------------------------------------------------- + + def build_lritems(self): + for p in self.Productions: + lastlri = p + i = 0 + lr_items = [] + while True: + if i > len(p): + lri = None + else: + lri = LRItem(p, i) + # Precompute the list of productions immediately following + try: + lri.lr_after = self.Prodnames[lri.prod[i+1]] + except (IndexError, KeyError): + lri.lr_after = [] + try: + lri.lr_before = lri.prod[i-1] + except IndexError: + lri.lr_before = None + + lastlri.lr_next = lri + if not lri: + break + lr_items.append(lri) + lastlri = lri + i += 1 + p.lr_items = lr_items + +# ----------------------------------------------------------------------------- +# == Class LRTable == +# +# This basic class represents a basic table of LR parsing information. +# Methods for generating the tables are not defined here. They are defined +# in the derived class LRGeneratedTable. +# ----------------------------------------------------------------------------- + +class VersionError(YaccError): + pass + +class LRTable(object): + def __init__(self): + self.lr_action = None + self.lr_goto = None + self.lr_productions = None + self.lr_method = None + + def read_table(self, module): + if isinstance(module, types.ModuleType): + parsetab = module + else: + exec('import %s' % module) + parsetab = sys.modules[module] + + if parsetab._tabversion != __tabversion__: + raise VersionError('yacc table file version is out of date') + + self.lr_action = parsetab._lr_action + self.lr_goto = parsetab._lr_goto + + self.lr_productions = [] + for p in parsetab._lr_productions: + self.lr_productions.append(MiniProduction(*p)) + + self.lr_method = parsetab._lr_method + return parsetab._lr_signature + + def read_pickle(self, filename): + try: + import cPickle as pickle + except ImportError: + import pickle + + if not os.path.exists(filename): + raise ImportError + + in_f = open(filename, 'rb') + + tabversion = pickle.load(in_f) + if tabversion != __tabversion__: + raise VersionError('yacc table file version is out of date') + self.lr_method = pickle.load(in_f) + signature = pickle.load(in_f) + self.lr_action = pickle.load(in_f) + self.lr_goto = pickle.load(in_f) + productions = pickle.load(in_f) + + self.lr_productions = [] + for p in productions: + self.lr_productions.append(MiniProduction(*p)) + + in_f.close() + return signature + + # Bind all production function names to callable objects in pdict + def bind_callables(self, pdict): + for p in self.lr_productions: + p.bind(pdict) + + +# ----------------------------------------------------------------------------- +# === LR Generator === +# +# The following classes and functions are used to generate LR parsing tables on +# a grammar. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# digraph() +# traverse() +# +# The following two functions are used to compute set valued functions +# of the form: +# +# F(x) = F'(x) U U{F(y) | x R y} +# +# This is used to compute the values of Read() sets as well as FOLLOW sets +# in LALR(1) generation. +# +# Inputs: X - An input set +# R - A relation +# FP - Set-valued function +# ------------------------------------------------------------------------------ + +def digraph(X, R, FP): + N = {} + for x in X: + N[x] = 0 + stack = [] + F = {} + for x in X: + if N[x] == 0: + traverse(x, N, stack, F, X, R, FP) + return F + +def traverse(x, N, stack, F, X, R, FP): + stack.append(x) + d = len(stack) + N[x] = d + F[x] = FP(x) # F(X) <- F'(x) + + rel = R(x) # Get y's related to x + for y in rel: + if N[y] == 0: + traverse(y, N, stack, F, X, R, FP) + N[x] = min(N[x], N[y]) + for a in F.get(y, []): + if a not in F[x]: + F[x].append(a) + if N[x] == d: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + +class LALRError(YaccError): + pass + +# ----------------------------------------------------------------------------- +# == LRGeneratedTable == +# +# This class implements the LR table generation algorithm. There are no +# public methods except for write() +# ----------------------------------------------------------------------------- + +class LRGeneratedTable(LRTable): + def __init__(self, grammar, method='LALR', log=None): + if method not in ['SLR', 'LALR']: + raise LALRError('Unsupported method %s' % method) + + self.grammar = grammar + self.lr_method = method + + # Set up the logger + if not log: + log = NullLogger() + self.log = log + + # Internal attributes + self.lr_action = {} # Action table + self.lr_goto = {} # Goto table + self.lr_productions = grammar.Productions # Copy of grammar Production array + self.lr_goto_cache = {} # Cache of computed gotos + self.lr0_cidhash = {} # Cache of closures + + self._add_count = 0 # Internal counter used to detect cycles + + # Diagonistic information filled in by the table generator + self.sr_conflict = 0 + self.rr_conflict = 0 + self.conflicts = [] # List of conflicts + + self.sr_conflicts = [] + self.rr_conflicts = [] + + # Build the tables + self.grammar.build_lritems() + self.grammar.compute_first() + self.grammar.compute_follow() + self.lr_parse_table() + + # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. + + def lr0_closure(self, I): + self._add_count += 1 + + # Add everything in I to J + J = I[:] + didadd = True + while didadd: + didadd = False + for j in J: + for x in j.lr_after: + if getattr(x, 'lr0_added', 0) == self._add_count: + continue + # Add B --> .G to J + J.append(x.lr_next) + x.lr0_added = self._add_count + didadd = True + + return J + + # Compute the LR(0) goto function goto(I,X) where I is a set + # of LR(0) items and X is a grammar symbol. This function is written + # in a way that guarantees uniqueness of the generated goto sets + # (i.e. the same goto set will never be returned as two different Python + # objects). With uniqueness, we can later do fast set comparisons using + # id(obj) instead of element-wise comparison. + + def lr0_goto(self, I, x): + # First we look for a previously cached entry + g = self.lr_goto_cache.get((id(I), x)) + if g: + return g + + # Now we generate the goto set in a way that guarantees uniqueness + # of the result + + s = self.lr_goto_cache.get(x) + if not s: + s = {} + self.lr_goto_cache[x] = s + + gs = [] + for p in I: + n = p.lr_next + if n and n.lr_before == x: + s1 = s.get(id(n)) + if not s1: + s1 = {} + s[id(n)] = s1 + gs.append(n) + s = s1 + g = s.get('$end') + if not g: + if gs: + g = self.lr0_closure(gs) + s['$end'] = g + else: + s['$end'] = gs + self.lr_goto_cache[(id(I), x)] = g + return g + + # Compute the LR(0) sets of item function + def lr0_items(self): + C = [self.lr0_closure([self.grammar.Productions[0].lr_next])] + i = 0 + for I in C: + self.lr0_cidhash[id(I)] = i + i += 1 + + # Loop over the items in C and each grammar symbols + i = 0 + while i < len(C): + I = C[i] + i += 1 + + # Collect all of the symbols that could possibly be in the goto(I,X) sets + asyms = {} + for ii in I: + for s in ii.usyms: + asyms[s] = None + + for x in asyms: + g = self.lr0_goto(I, x) + if not g or id(g) in self.lr0_cidhash: + continue + self.lr0_cidhash[id(g)] = len(C) + C.append(g) + + return C + + # ----------------------------------------------------------------------------- + # ==== LALR(1) Parsing ==== + # + # LALR(1) parsing is almost exactly the same as SLR except that instead of + # relying upon Follow() sets when performing reductions, a more selective + # lookahead set that incorporates the state of the LR(0) machine is utilized. + # Thus, we mainly just have to focus on calculating the lookahead sets. + # + # The method used here is due to DeRemer and Pennelo (1982). + # + # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) + # Lookahead Sets", ACM Transactions on Programming Languages and Systems, + # Vol. 4, No. 4, Oct. 1982, pp. 615-649 + # + # Further details can also be found in: + # + # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", + # McGraw-Hill Book Company, (1985). + # + # ----------------------------------------------------------------------------- + + # ----------------------------------------------------------------------------- + # compute_nullable_nonterminals() + # + # Creates a dictionary containing all of the non-terminals that might produce + # an empty production. + # ----------------------------------------------------------------------------- + + def compute_nullable_nonterminals(self): + nullable = set() + num_nullable = 0 + while True: + for p in self.grammar.Productions[1:]: + if p.len == 0: + nullable.add(p.name) + continue + for t in p.prod: + if t not in nullable: + break + else: + nullable.add(p.name) + if len(nullable) == num_nullable: + break + num_nullable = len(nullable) + return nullable + + # ----------------------------------------------------------------------------- + # find_nonterminal_trans(C) + # + # Given a set of LR(0) items, this functions finds all of the non-terminal + # transitions. These are transitions in which a dot appears immediately before + # a non-terminal. Returns a list of tuples of the form (state,N) where state + # is the state number and N is the nonterminal symbol. + # + # The input C is the set of LR(0) items. + # ----------------------------------------------------------------------------- + + def find_nonterminal_transitions(self, C): + trans = [] + for stateno, state in enumerate(C): + for p in state: + if p.lr_index < p.len - 1: + t = (stateno, p.prod[p.lr_index+1]) + if t[1] in self.grammar.Nonterminals: + if t not in trans: + trans.append(t) + return trans + + # ----------------------------------------------------------------------------- + # dr_relation() + # + # Computes the DR(p,A) relationships for non-terminal transitions. The input + # is a tuple (state,N) where state is a number and N is a nonterminal symbol. + # + # Returns a list of terminals. + # ----------------------------------------------------------------------------- + + def dr_relation(self, C, trans, nullable): + state, N = trans + terms = [] + + g = self.lr0_goto(C[state], N) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if a in self.grammar.Terminals: + if a not in terms: + terms.append(a) + + # This extra bit is to handle the start state + if state == 0 and N == self.grammar.Productions[0].prod[0]: + terms.append('$end') + + return terms + + # ----------------------------------------------------------------------------- + # reads_relation() + # + # Computes the READS() relation (p,A) READS (t,C). + # ----------------------------------------------------------------------------- + + def reads_relation(self, C, trans, empty): + # Look for empty transitions + rel = [] + state, N = trans + + g = self.lr0_goto(C[state], N) + j = self.lr0_cidhash.get(id(g), -1) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index + 1] + if a in empty: + rel.append((j, a)) + + return rel + + # ----------------------------------------------------------------------------- + # compute_lookback_includes() + # + # Determines the lookback and includes relations + # + # LOOKBACK: + # + # This relation is determined by running the LR(0) state machine forward. + # For example, starting with a production "N : . A B C", we run it forward + # to obtain "N : A B C ." We then build a relationship between this final + # state and the starting state. These relationships are stored in a dictionary + # lookdict. + # + # INCLUDES: + # + # Computes the INCLUDE() relation (p,A) INCLUDES (p',B). + # + # This relation is used to determine non-terminal transitions that occur + # inside of other non-terminal transition states. (p,A) INCLUDES (p', B) + # if the following holds: + # + # B -> LAT, where T -> epsilon and p' -L-> p + # + # L is essentially a prefix (which may be empty), T is a suffix that must be + # able to derive an empty string. State p' must lead to state p with the string L. + # + # ----------------------------------------------------------------------------- + + def compute_lookback_includes(self, C, trans, nullable): + lookdict = {} # Dictionary of lookback relations + includedict = {} # Dictionary of include relations + + # Make a dictionary of non-terminal transitions + dtrans = {} + for t in trans: + dtrans[t] = 1 + + # Loop over all transitions and compute lookbacks and includes + for state, N in trans: + lookb = [] + includes = [] + for p in C[state]: + if p.name != N: + continue + + # Okay, we have a name match. We now follow the production all the way + # through the state machine until we get the . on the right hand side + + lr_index = p.lr_index + j = state + while lr_index < p.len - 1: + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if (j, t) in dtrans: + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if p.prod[li] in self.grammar.Terminals: + break # No forget it + if p.prod[li] not in nullable: + break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j, t)) + + g = self.lr0_goto(C[j], t) # Go to next set + j = self.lr0_cidhash.get(id(g), -1) # Go to next state + + # When we get here, j is the final state, now we have to locate the production + for r in C[j]: + if r.name != p.name: + continue + if r.len != p.len: + continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: + break + i = i + 1 + else: + lookb.append((j, r)) + for i in includes: + if i not in includedict: + includedict[i] = [] + includedict[i].append((state, N)) + lookdict[(state, N)] = lookb + + return lookdict, includedict + + # ----------------------------------------------------------------------------- + # compute_read_sets() + # + # Given a set of LR(0) items, this function computes the read sets. + # + # Inputs: C = Set of LR(0) items + # ntrans = Set of nonterminal transitions + # nullable = Set of empty transitions + # + # Returns a set containing the read sets + # ----------------------------------------------------------------------------- + + def compute_read_sets(self, C, ntrans, nullable): + FP = lambda x: self.dr_relation(C, x, nullable) + R = lambda x: self.reads_relation(C, x, nullable) + F = digraph(ntrans, R, FP) + return F + + # ----------------------------------------------------------------------------- + # compute_follow_sets() + # + # Given a set of LR(0) items, a set of non-terminal transitions, a readset, + # and an include set, this function computes the follow sets + # + # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} + # + # Inputs: + # ntrans = Set of nonterminal transitions + # readsets = Readset (previously computed) + # inclsets = Include sets (previously computed) + # + # Returns a set containing the follow sets + # ----------------------------------------------------------------------------- + + def compute_follow_sets(self, ntrans, readsets, inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x, []) + F = digraph(ntrans, R, FP) + return F + + # ----------------------------------------------------------------------------- + # add_lookaheads() + # + # Attaches the lookahead symbols to grammar rules. + # + # Inputs: lookbacks - Set of lookback relations + # followset - Computed follow set + # + # This function directly attaches the lookaheads to productions contained + # in the lookbacks set + # ----------------------------------------------------------------------------- + + def add_lookaheads(self, lookbacks, followset): + for trans, lb in lookbacks.items(): + # Loop over productions in lookback + for state, p in lb: + if state not in p.lookaheads: + p.lookaheads[state] = [] + f = followset.get(trans, []) + for a in f: + if a not in p.lookaheads[state]: + p.lookaheads[state].append(a) + + # ----------------------------------------------------------------------------- + # add_lalr_lookaheads() + # + # This function does all of the work of adding lookahead information for use + # with LALR parsing + # ----------------------------------------------------------------------------- + + def add_lalr_lookaheads(self, C): + # Determine all of the nullable nonterminals + nullable = self.compute_nullable_nonterminals() + + # Find all non-terminal transitions + trans = self.find_nonterminal_transitions(C) + + # Compute read sets + readsets = self.compute_read_sets(C, trans, nullable) + + # Compute lookback/includes relations + lookd, included = self.compute_lookback_includes(C, trans, nullable) + + # Compute LALR FOLLOW sets + followsets = self.compute_follow_sets(trans, readsets, included) + + # Add all of the lookaheads + self.add_lookaheads(lookd, followsets) + + # ----------------------------------------------------------------------------- + # lr_parse_table() + # + # This function constructs the parse tables for SLR or LALR + # ----------------------------------------------------------------------------- + def lr_parse_table(self): + Productions = self.grammar.Productions + Precedence = self.grammar.Precedence + goto = self.lr_goto # Goto array + action = self.lr_action # Action array + log = self.log # Logger for output + + actionp = {} # Action production array (temporary) + + log.info('Parsing method: %s', self.lr_method) + + # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items + # This determines the number of states + + C = self.lr0_items() + + if self.lr_method == 'LALR': + self.add_lalr_lookaheads(C) + + # Build the parser table, state by state + st = 0 + for I in C: + # Loop over each production in I + actlist = [] # List of actions + st_action = {} + st_actionp = {} + st_goto = {} + log.info('') + log.info('state %d', st) + log.info('') + for p in I: + log.info(' (%d) %s', p.number, p) + log.info('') + + for p in I: + if p.len == p.lr_index + 1: + if p.name == "S'": + # Start symbol. Accept! + st_action['$end'] = 0 + st_actionp['$end'] = p + else: + # We are at the end of a production. Reduce! + if self.lr_method == 'LALR': + laheads = p.lookaheads[st] + else: + laheads = self.grammar.Follow[p.name] + for a in laheads: + actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p))) + r = st_action.get(a) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from rule being reduced (p) + rprec, rlevel = Productions[p.number].prec + + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + st_action[a] = -p.number + st_actionp[a] = p + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + Productions[p.number].reduced += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + st_action[a] = -p.number + st_actionp[a] = p + chosenp, rejectp = pp, oldp + Productions[p.number].reduced += 1 + Productions[oldp.number].reduced -= 1 + else: + chosenp, rejectp = oldp, pp + self.rr_conflicts.append((st, chosenp, rejectp)) + log.info(' ! reduce/reduce conflict for %s resolved using rule %d (%s)', + a, st_actionp[a].number, st_actionp[a]) + else: + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = -p.number + st_actionp[a] = p + Productions[p.number].reduced += 1 + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if a in self.grammar.Terminals: + g = self.lr0_goto(I, a) + j = self.lr0_cidhash.get(id(g), -1) + if j >= 0: + # We are in a shift state + actlist.append((a, p, 'shift and go to state %d' % j)) + r = st_action.get(a) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + raise LALRError('Shift/shift conflict in state %d' % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from the rule that could have been reduced + rprec, rlevel = Productions[st_actionp[a].number].prec + + if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): + # We decide to shift here... highest precedence to shift + Productions[st_actionp[a].number].reduced -= 1 + st_action[a] = j + st_actionp[a] = p + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + + else: + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = j + st_actionp[a] = p + + # Print the actions associated with each terminal + _actprint = {} + for a, p, m in actlist: + if a in st_action: + if p is st_actionp[a]: + log.info(' %-15s %s', a, m) + _actprint[(a, m)] = 1 + log.info('') + # Print the actions that were not used. (debugging) + not_used = 0 + for a, p, m in actlist: + if a in st_action: + if p is not st_actionp[a]: + if not (a, m) in _actprint: + log.debug(' ! %-15s [ %s ]', a, m) + not_used = 1 + _actprint[(a, m)] = 1 + if not_used: + log.debug('') + + # Construct the goto table for this state + + nkeys = {} + for ii in I: + for s in ii.usyms: + if s in self.grammar.Nonterminals: + nkeys[s] = None + for n in nkeys: + g = self.lr0_goto(I, n) + j = self.lr0_cidhash.get(id(g), -1) + if j >= 0: + st_goto[n] = j + log.info(' %-30s shift and go to state %d', n, j) + + action[st] = st_action + actionp[st] = st_actionp + goto[st] = st_goto + st += 1 + + # ----------------------------------------------------------------------------- + # write() + # + # This function writes the LR parsing tables to a file + # ----------------------------------------------------------------------------- + + def write_table(self, tabmodule, outputdir='', signature=''): + if isinstance(tabmodule, types.ModuleType): + raise IOError("Won't overwrite existing tabmodule") + + basemodulename = tabmodule.split('.')[-1] + filename = os.path.join(outputdir, basemodulename) + '.py' + try: + f = open(filename, 'w') + + f.write(''' +# %s +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = %r + +_lr_method = %r + +_lr_signature = %r + ''' % (os.path.basename(filename), __tabversion__, self.lr_method, signature)) + + # Change smaller to 0 to go back to original tables + smaller = 1 + + # Factor out names to try and make smaller + if smaller: + items = {} + + for s, nd in self.lr_action.items(): + for name, v in nd.items(): + i = items.get(name) + if not i: + i = ([], []) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write('\n_lr_action_items = {') + for k, v in items.items(): + f.write('%r:([' % k) + for i in v[0]: + f.write('%r,' % i) + f.write('],[') + for i in v[1]: + f.write('%r,' % i) + + f.write(']),') + f.write('}\n') + + f.write(''' +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items +''') + + else: + f.write('\n_lr_action = { ') + for k, v in self.lr_action.items(): + f.write('(%r,%r):%r,' % (k[0], k[1], v)) + f.write('}\n') + + if smaller: + # Factor out names to try and make smaller + items = {} + + for s, nd in self.lr_goto.items(): + for name, v in nd.items(): + i = items.get(name) + if not i: + i = ([], []) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write('\n_lr_goto_items = {') + for k, v in items.items(): + f.write('%r:([' % k) + for i in v[0]: + f.write('%r,' % i) + f.write('],[') + for i in v[1]: + f.write('%r,' % i) + + f.write(']),') + f.write('}\n') + + f.write(''' +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +''') + else: + f.write('\n_lr_goto = { ') + for k, v in self.lr_goto.items(): + f.write('(%r,%r):%r,' % (k[0], k[1], v)) + f.write('}\n') + + # Write production table + f.write('_lr_productions = [\n') + for p in self.lr_productions: + if p.func: + f.write(' (%r,%r,%d,%r,%r,%d),\n' % (p.str, p.name, p.len, + p.func, os.path.basename(p.file), p.line)) + else: + f.write(' (%r,%r,%d,None,None,None),\n' % (str(p), p.name, p.len)) + f.write(']\n') + f.close() + + except IOError as e: + raise + + + # ----------------------------------------------------------------------------- + # pickle_table() + # + # This function pickles the LR parsing tables to a supplied file object + # ----------------------------------------------------------------------------- + + def pickle_table(self, filename, signature=''): + try: + import cPickle as pickle + except ImportError: + import pickle + with open(filename, 'wb') as outf: + pickle.dump(__tabversion__, outf, pickle_protocol) + pickle.dump(self.lr_method, outf, pickle_protocol) + pickle.dump(signature, outf, pickle_protocol) + pickle.dump(self.lr_action, outf, pickle_protocol) + pickle.dump(self.lr_goto, outf, pickle_protocol) + + outp = [] + for p in self.lr_productions: + if p.func: + outp.append((p.str, p.name, p.len, p.func, os.path.basename(p.file), p.line)) + else: + outp.append((str(p), p.name, p.len, None, None, None)) + pickle.dump(outp, outf, pickle_protocol) + +# ----------------------------------------------------------------------------- +# === INTROSPECTION === +# +# The following functions and classes are used to implement the PLY +# introspection features followed by the yacc() function itself. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# get_caller_module_dict() +# +# This function returns a dictionary containing all of the symbols defined within +# a caller further down the call stack. This is used to get the environment +# associated with the yacc() call if none was provided. +# ----------------------------------------------------------------------------- + +def get_caller_module_dict(levels): + f = sys._getframe(levels) + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + return ldict + +# ----------------------------------------------------------------------------- +# parse_grammar() +# +# This takes a raw grammar rule string and parses it into production data +# ----------------------------------------------------------------------------- +def parse_grammar(doc, file, line): + grammar = [] + # Split the doc string into lines + pstrings = doc.splitlines() + lastp = None + dline = line + for ps in pstrings: + dline += 1 + p = ps.split() + if not p: + continue + try: + if p[0] == '|': + # This is a continuation of a previous rule + if not lastp: + raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline)) + prodname = lastp + syms = p[1:] + else: + prodname = p[0] + lastp = prodname + syms = p[2:] + assign = p[1] + if assign != ':' and assign != '::=': + raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline)) + + grammar.append((file, dline, prodname, syms)) + except SyntaxError: + raise + except Exception: + raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip())) + + return grammar + +# ----------------------------------------------------------------------------- +# ParserReflect() +# +# This class represents information extracted for building a parser including +# start symbol, error function, tokens, precedence list, action functions, +# etc. +# ----------------------------------------------------------------------------- +class ParserReflect(object): + def __init__(self, pdict, log=None): + self.pdict = pdict + self.start = None + self.error_func = None + self.tokens = None + self.modules = set() + self.grammar = [] + self.error = False + + if log is None: + self.log = PlyLogger(sys.stderr) + else: + self.log = log + + # Get all of the basic information + def get_all(self): + self.get_start() + self.get_error_func() + self.get_tokens() + self.get_precedence() + self.get_pfunctions() + + # Validate all of the information + def validate_all(self): + self.validate_start() + self.validate_error_func() + self.validate_tokens() + self.validate_precedence() + self.validate_pfunctions() + self.validate_modules() + return self.error + + # Compute a signature over the grammar + def signature(self): + parts = [] + try: + if self.start: + parts.append(self.start) + if self.prec: + parts.append(''.join([''.join(p) for p in self.prec])) + if self.tokens: + parts.append(' '.join(self.tokens)) + for f in self.pfuncs: + if f[3]: + parts.append(f[3]) + except (TypeError, ValueError): + pass + return ''.join(parts) + + # ----------------------------------------------------------------------------- + # validate_modules() + # + # This method checks to see if there are duplicated p_rulename() functions + # in the parser module file. Without this function, it is really easy for + # users to make mistakes by cutting and pasting code fragments (and it's a real + # bugger to try and figure out why the resulting parser doesn't work). Therefore, + # we just do a little regular expression pattern matching of def statements + # to try and detect duplicates. + # ----------------------------------------------------------------------------- + + def validate_modules(self): + # Match def p_funcname( + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') + + for module in self.modules: + try: + lines, linen = inspect.getsourcelines(module) + except IOError: + continue + + counthash = {} + for linen, line in enumerate(lines): + linen += 1 + m = fre.match(line) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + filename = inspect.getsourcefile(module) + self.log.warning('%s:%d: Function %s redefined. Previously defined on line %d', + filename, linen, name, prev) + + # Get the start symbol + def get_start(self): + self.start = self.pdict.get('start') + + # Validate the start symbol + def validate_start(self): + if self.start is not None: + if not isinstance(self.start, string_types): + self.log.error("'start' must be a string") + + # Look for error handler + def get_error_func(self): + self.error_func = self.pdict.get('p_error') + + # Validate the error function + def validate_error_func(self): + if self.error_func: + if isinstance(self.error_func, types.FunctionType): + ismethod = 0 + elif isinstance(self.error_func, types.MethodType): + ismethod = 1 + else: + self.log.error("'p_error' defined, but is not a function or method") + self.error = True + return + + eline = self.error_func.__code__.co_firstlineno + efile = self.error_func.__code__.co_filename + module = inspect.getmodule(self.error_func) + self.modules.add(module) + + argcount = self.error_func.__code__.co_argcount - ismethod + if argcount != 1: + self.log.error('%s:%d: p_error() requires 1 argument', efile, eline) + self.error = True + + # Get the tokens map + def get_tokens(self): + tokens = self.pdict.get('tokens') + if not tokens: + self.log.error('No token list is defined') + self.error = True + return + + if not isinstance(tokens, (list, tuple)): + self.log.error('tokens must be a list or tuple') + self.error = True + return + + if not tokens: + self.log.error('tokens is empty') + self.error = True + return + + self.tokens = sorted(tokens) + + # Validate the tokens + def validate_tokens(self): + # Validate the tokens. + if 'error' in self.tokens: + self.log.error("Illegal token name 'error'. Is a reserved word") + self.error = True + return + + terminals = set() + for n in self.tokens: + if n in terminals: + self.log.warning('Token %r multiply defined', n) + terminals.add(n) + + # Get the precedence map (if any) + def get_precedence(self): + self.prec = self.pdict.get('precedence') + + # Validate and parse the precedence map + def validate_precedence(self): + preclist = [] + if self.prec: + if not isinstance(self.prec, (list, tuple)): + self.log.error('precedence must be a list or tuple') + self.error = True + return + for level, p in enumerate(self.prec): + if not isinstance(p, (list, tuple)): + self.log.error('Bad precedence table') + self.error = True + return + + if len(p) < 2: + self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p) + self.error = True + return + assoc = p[0] + if not isinstance(assoc, string_types): + self.log.error('precedence associativity must be a string') + self.error = True + return + for term in p[1:]: + if not isinstance(term, string_types): + self.log.error('precedence items must be strings') + self.error = True + return + preclist.append((term, assoc, level+1)) + self.preclist = preclist + + # Get all p_functions from the grammar + def get_pfunctions(self): + p_functions = [] + for name, item in self.pdict.items(): + if not name.startswith('p_') or name == 'p_error': + continue + if isinstance(item, (types.FunctionType, types.MethodType)): + line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno) + module = inspect.getmodule(item) + p_functions.append((line, module, name, item.__doc__)) + + # Sort all of the actions by line number; make sure to stringify + # modules to make them sortable, since `line` may not uniquely sort all + # p functions + p_functions.sort(key=lambda p_function: ( + p_function[0], + str(p_function[1]), + p_function[2], + p_function[3])) + self.pfuncs = p_functions + + # Validate all of the p_functions + def validate_pfunctions(self): + grammar = [] + # Check for non-empty symbols + if len(self.pfuncs) == 0: + self.log.error('no rules of the form p_rulename are defined') + self.error = True + return + + for line, module, name, doc in self.pfuncs: + file = inspect.getsourcefile(module) + func = self.pdict[name] + if isinstance(func, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + if func.__code__.co_argcount > reqargs: + self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__) + self.error = True + elif func.__code__.co_argcount < reqargs: + self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__) + self.error = True + elif not func.__doc__: + self.log.warning('%s:%d: No documentation string specified in function %r (ignored)', + file, line, func.__name__) + else: + try: + parsed_g = parse_grammar(doc, file, line) + for g in parsed_g: + grammar.append((name, g)) + except SyntaxError as e: + self.log.error(str(e)) + self.error = True + + # Looks like a valid grammar rule + # Mark the file in which defined. + self.modules.add(module) + + # Secondary validation step that looks for p_ definitions that are not functions + # or functions that look like they might be grammar rules. + + for n, v in self.pdict.items(): + if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)): + continue + if n.startswith('t_'): + continue + if n.startswith('p_') and n != 'p_error': + self.log.warning('%r not defined as a function', n) + if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or + (isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)): + if v.__doc__: + try: + doc = v.__doc__.split(' ') + if doc[1] == ':': + self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix', + v.__code__.co_filename, v.__code__.co_firstlineno, n) + except IndexError: + pass + + self.grammar = grammar + +# ----------------------------------------------------------------------------- +# yacc(module) +# +# Build a parser +# ----------------------------------------------------------------------------- + +def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, + check_recursion=True, optimize=False, write_tables=True, debugfile=debug_file, + outputdir=None, debuglog=None, errorlog=None, picklefile=None): + + if tabmodule is None: + tabmodule = tab_module + + # Reference to the parsing method of the last built parser + global parse + + # If pickling is enabled, table files are not created + if picklefile: + write_tables = 0 + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the parser + if module: + _items = [(k, getattr(module, k)) for k in dir(module)] + pdict = dict(_items) + # If no __file__ or __package__ attributes are available, try to obtain them + # from the __module__ instead + if '__file__' not in pdict: + pdict['__file__'] = sys.modules[pdict['__module__']].__file__ + if '__package__' not in pdict and '__module__' in pdict: + if hasattr(sys.modules[pdict['__module__']], '__package__'): + pdict['__package__'] = sys.modules[pdict['__module__']].__package__ + else: + pdict = get_caller_module_dict(2) + + if outputdir is None: + # If no output directory is set, the location of the output files + # is determined according to the following rules: + # - If tabmodule specifies a package, files go into that package directory + # - Otherwise, files go in the same directory as the specifying module + if isinstance(tabmodule, types.ModuleType): + srcfile = tabmodule.__file__ + else: + if '.' not in tabmodule: + srcfile = pdict['__file__'] + else: + parts = tabmodule.split('.') + pkgname = '.'.join(parts[:-1]) + exec('import %s' % pkgname) + srcfile = getattr(sys.modules[pkgname], '__file__', '') + outputdir = os.path.dirname(srcfile) + + # Determine if the module is package of a package or not. + # If so, fix the tabmodule setting so that tables load correctly + pkg = pdict.get('__package__') + if pkg and isinstance(tabmodule, str): + if '.' not in tabmodule: + tabmodule = pkg + '.' + tabmodule + + + + # Set start symbol if it's specified directly using an argument + if start is not None: + pdict['start'] = start + + # Collect parser information from the dictionary + pinfo = ParserReflect(pdict, log=errorlog) + pinfo.get_all() + + if pinfo.error: + raise YaccError('Unable to build parser') + + # Check signature against table files (if any) + signature = pinfo.signature() + + # Read the tables + try: + lr = LRTable() + if picklefile: + read_signature = lr.read_pickle(picklefile) + else: + read_signature = lr.read_table(tabmodule) + if optimize or (read_signature == signature): + try: + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr, pinfo.error_func) + parse = parser.parse + return parser + except Exception as e: + errorlog.warning('There was a problem loading the table file: %r', e) + except VersionError as e: + errorlog.warning(str(e)) + except ImportError: + pass + + if debuglog is None: + if debug: + try: + debuglog = PlyLogger(open(os.path.join(outputdir, debugfile), 'w')) + except IOError as e: + errorlog.warning("Couldn't open %r. %s" % (debugfile, e)) + debuglog = NullLogger() + else: + debuglog = NullLogger() + + debuglog.info('Created by PLY version %s (http://www.dabeaz.com/ply)', __version__) + + errors = False + + # Validate the parser information + if pinfo.validate_all(): + raise YaccError('Unable to build parser') + + if not pinfo.error_func: + errorlog.warning('no p_error() function is defined') + + # Create a grammar object + grammar = Grammar(pinfo.tokens) + + # Set precedence level for terminals + for term, assoc, level in pinfo.preclist: + try: + grammar.set_precedence(term, assoc, level) + except GrammarError as e: + errorlog.warning('%s', e) + + # Add productions to the grammar + for funcname, gram in pinfo.grammar: + file, line, prodname, syms = gram + try: + grammar.add_production(prodname, syms, funcname, file, line) + except GrammarError as e: + errorlog.error('%s', e) + errors = True + + # Set the grammar start symbols + try: + if start is None: + grammar.set_start(pinfo.start) + else: + grammar.set_start(start) + except GrammarError as e: + errorlog.error(str(e)) + errors = True + + if errors: + raise YaccError('Unable to build parser') + + # Verify the grammar structure + undefined_symbols = grammar.undefined_symbols() + for sym, prod in undefined_symbols: + errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym) + errors = True + + unused_terminals = grammar.unused_terminals() + if unused_terminals: + debuglog.info('') + debuglog.info('Unused terminals:') + debuglog.info('') + for term in unused_terminals: + errorlog.warning('Token %r defined, but not used', term) + debuglog.info(' %s', term) + + # Print out all productions to the debug log + if debug: + debuglog.info('') + debuglog.info('Grammar') + debuglog.info('') + for n, p in enumerate(grammar.Productions): + debuglog.info('Rule %-5d %s', n, p) + + # Find unused non-terminals + unused_rules = grammar.unused_rules() + for prod in unused_rules: + errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name) + + if len(unused_terminals) == 1: + errorlog.warning('There is 1 unused token') + if len(unused_terminals) > 1: + errorlog.warning('There are %d unused tokens', len(unused_terminals)) + + if len(unused_rules) == 1: + errorlog.warning('There is 1 unused rule') + if len(unused_rules) > 1: + errorlog.warning('There are %d unused rules', len(unused_rules)) + + if debug: + debuglog.info('') + debuglog.info('Terminals, with rules where they appear') + debuglog.info('') + terms = list(grammar.Terminals) + terms.sort() + for term in terms: + debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]])) + + debuglog.info('') + debuglog.info('Nonterminals, with rules where they appear') + debuglog.info('') + nonterms = list(grammar.Nonterminals) + nonterms.sort() + for nonterm in nonterms: + debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]])) + debuglog.info('') + + if check_recursion: + unreachable = grammar.find_unreachable() + for u in unreachable: + errorlog.warning('Symbol %r is unreachable', u) + + infinite = grammar.infinite_cycles() + for inf in infinite: + errorlog.error('Infinite recursion detected for symbol %r', inf) + errors = True + + unused_prec = grammar.unused_precedence() + for term, assoc in unused_prec: + errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term) + errors = True + + if errors: + raise YaccError('Unable to build parser') + + # Run the LRGeneratedTable on the grammar + if debug: + errorlog.debug('Generating %s tables', method) + + lr = LRGeneratedTable(grammar, method, debuglog) + + if debug: + num_sr = len(lr.sr_conflicts) + + # Report shift/reduce and reduce/reduce conflicts + if num_sr == 1: + errorlog.warning('1 shift/reduce conflict') + elif num_sr > 1: + errorlog.warning('%d shift/reduce conflicts', num_sr) + + num_rr = len(lr.rr_conflicts) + if num_rr == 1: + errorlog.warning('1 reduce/reduce conflict') + elif num_rr > 1: + errorlog.warning('%d reduce/reduce conflicts', num_rr) + + # Write out conflicts to the output file + if debug and (lr.sr_conflicts or lr.rr_conflicts): + debuglog.warning('') + debuglog.warning('Conflicts:') + debuglog.warning('') + + for state, tok, resolution in lr.sr_conflicts: + debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s', tok, state, resolution) + + already_reported = set() + for state, rule, rejected in lr.rr_conflicts: + if (state, id(rule), id(rejected)) in already_reported: + continue + debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + debuglog.warning('rejected rule (%s) in state %d', rejected, state) + errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + errorlog.warning('rejected rule (%s) in state %d', rejected, state) + already_reported.add((state, id(rule), id(rejected))) + + warned_never = [] + for state, rule, rejected in lr.rr_conflicts: + if not rejected.reduced and (rejected not in warned_never): + debuglog.warning('Rule (%s) is never reduced', rejected) + errorlog.warning('Rule (%s) is never reduced', rejected) + warned_never.append(rejected) + + # Write the table file if requested + if write_tables: + try: + lr.write_table(tabmodule, outputdir, signature) + if tabmodule in sys.modules: + del sys.modules[tabmodule] + except IOError as e: + errorlog.warning("Couldn't create %r. %s" % (tabmodule, e)) + + # Write a pickled version of the tables + if picklefile: + try: + lr.pickle_table(picklefile, signature) + except IOError as e: + errorlog.warning("Couldn't create %r. %s" % (picklefile, e)) + + # Build the parser + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr, pinfo.error_func) + + parse = parser.parse + return parser diff --git a/docs/configuration.rst b/docs/configuration.rst index 3afe7ee817f..c75997b548a 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -659,7 +659,46 @@ The following environment variables for the tracer are supported: if no records are returned. version_added: v2.6.0: + + DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING: + type: String + default: None + description: | + Enables AWS request payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. + version_added: + v2.17.0: + + DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING: + type: String + default: None + description: | + Enables AWS response payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. + version_added: + v2.17.0: + + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH: + type: Integer + default: 10 + description: | + Sets the depth of expanding the JSON AWS payload after which we stop creating tags. + version_added: + v2.17.0: + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_TAGS: + type: Integer + default: 758 + description: | + Sets the the maximum number of tags that will be added when expanding an AWS payload. + version_added: + v2.17.0: + + DD_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES: + type: Set + default: {"s3", "sns", "sqs", "kinesis", "eventbridge"} + description: | + Sets the enabled AWS services to be expanded when AWS payload tagging is enabled. + version_added: + v2.17.0: .. _Unified Service Tagging: https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/ diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index cb88fa64a33..ed29d8fd07d 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -92,6 +92,7 @@ entrypoints env enqueuer eol +eventbridge exec fastapi formatter @@ -123,6 +124,7 @@ JSON jinja js kafka +kinesis kombu kubernetes kwarg @@ -218,9 +220,11 @@ sanic screenshots serializable sha +sns sql sqlalchemy sqlite +sqs stacktrace starlette statsd diff --git a/releasenotes/notes/add-aws-payload-tagging-d01f0033c7e1f5c0.yaml b/releasenotes/notes/add-aws-payload-tagging-d01f0033c7e1f5c0.yaml new file mode 100644 index 00000000000..4a71119eb80 --- /dev/null +++ b/releasenotes/notes/add-aws-payload-tagging-d01f0033c7e1f5c0.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for expanding AWS request/response Payloads into flattened span tags. + diff --git a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py index 4b0812b4807..6a10f2b53f4 100644 --- a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py +++ b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py @@ -146,7 +146,7 @@ def _uninstall_watchdog_and_reload(): del sys.modules["tests.appsec.iast.fixtures.entrypoint.views"] -@pytest.mark.subprocess() +@pytest.mark.subprocess(check_logs=False) def test_ddtrace_iast_flask_app_create_app_patch_all_enable_iast_propagation(): import dis import io diff --git a/tests/conftest.py b/tests/conftest.py index be1790e432f..c4c21caddf0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -299,6 +299,7 @@ def run_function_from_file(item, params=None): args = [sys.executable] timeout = marker.kwargs.get("timeout", None) + check_logs = marker.kwargs.get("check_logs", True) # Add ddtrace-run prefix in ddtrace-run mode if marker.kwargs.get("ddtrace_run", False): @@ -367,10 +368,16 @@ def _subprocess_wrapper(): ) if not is_stream_ok(out, expected_out): - raise AssertionError("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) + if check_logs: + raise AssertionError("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) + else: + pytest.xfail("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) if not is_stream_ok(err, expected_err): - raise AssertionError("STDERR: Expected [%s] got [%s]" % (expected_err, err)) + if check_logs: + raise AssertionError("STDERR: Expected [%s] got [%s]" % (expected_err, err)) + else: + pytest.xfail("STDOUT: Expected [%s] got [%s]" % (expected_out, out)) return _subprocess_wrapper() finally: diff --git a/tests/contrib/botocore/test.py b/tests/contrib/botocore/test.py index 657924be005..4910f32ac46 100644 --- a/tests/contrib/botocore/test.py +++ b/tests/contrib/botocore/test.py @@ -55,6 +55,16 @@ # Parse botocore.__version_ from "1.9.0" to (1, 9, 0) BOTOCORE_VERSION = parse_version(botocore.__version__) +# Span data which isn't static to ignore in the snapshots. +snapshot_ignores = [ + "meta.aws.response.body.HTTPHeaders.date", + "meta.aws.requestid", + "meta.aws.response.body.RequestId", + "meta.aws.response.body.HTTPHeaders.content-length", + "meta.aws.response.body.HTTPHeaders.x-amzn-requestid", + "meta.error.stack", +] + def get_zip_lambda(): code = """ @@ -3765,3 +3775,281 @@ def test_schematized_unspecified_service_secretsmanager_v1(self): assert span.service == DEFAULT_SPAN_SERVICE_NAME assert span.name == "aws.secretsmanager.request" + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_sqs + def test_aws_payload_tagging_sqs(self): + with self.override_config("botocore", dict(payload_tagging_request="all", payload_tagging_response="all")): + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(self.sqs_client) + message_attributes = { + "one": {"DataType": "String", "StringValue": "one"}, + "two": {"DataType": "String", "StringValue": "two"}, + "three": {"DataType": "String", "StringValue": "three"}, + "four": {"DataType": "String", "StringValue": "four"}, + "five": {"DataType": "String", "StringValue": "five"}, + "six": {"DataType": "String", "StringValue": "six"}, + "seven": {"DataType": "String", "StringValue": "seven"}, + "eight": {"DataType": "String", "StringValue": "eight"}, + "nine": {"DataType": "String", "StringValue": "nine"}, + "ten": {"DataType": "String", "StringValue": "ten"}, + } + self.sqs_client.send_message( + QueueUrl=self.sqs_test_queue["QueueUrl"], MessageBody="world", MessageAttributes=message_attributes + ) + spans = self.get_spans() + assert spans + assert len(spans) == 1 + span = spans[0] + assert span.get_tag("aws.region") == "us-east-1" + assert span.get_tag("region") == "us-east-1" + assert span.get_tag("aws.operation") == "SendMessage" + assert span.get_tag("params.MessageBody") is None + assert span.get_tag("component") == "botocore" + assert span.get_tag("span.kind"), "client" + assert_is_measured(span) + assert_span_http_status_code(span, 200) + assert span.service == "test-botocore-tracing.sqs" + assert span.resource == "sqs.sendmessage" + trace_json = span.get_tag("params.MessageAttributes._datadog.StringValue") + assert trace_json is None + response = self.sqs_client.receive_message( + QueueUrl=self.sqs_test_queue["QueueUrl"], + MessageAttributeNames=["_datadog"], + WaitTimeSeconds=2, + ) + assert len(response["Messages"]) == 1 + trace_in_message = "MessageAttributes" in response["Messages"][0] + assert trace_in_message is False + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_sns + @mock_sqs + def test_aws_payload_tagging_sns(self): + with self.override_config("botocore", dict(payload_tagging_request="all", payload_tagging_response="all")): + region = "us-east-1" + sns = self.session.create_client("sns", region_name=region, endpoint_url="http://localhost:4566") + + topic = sns.create_topic(Name="testTopic") + + topic_arn = topic["TopicArn"] + sqs_url = self.sqs_test_queue["QueueUrl"] + url_parts = sqs_url.split("/") + sqs_arn = "arn:aws:sqs:{}:{}:{}".format(region, url_parts[-2], url_parts[-1]) + sns.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint=sqs_arn) + + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(sns) + + message_attributes = { + "one": {"DataType": "String", "StringValue": "one"}, + "two": {"DataType": "String", "StringValue": "two"}, + "three": {"DataType": "String", "StringValue": "three"}, + "four": {"DataType": "String", "StringValue": "four"}, + "five": {"DataType": "String", "StringValue": "five"}, + "six": {"DataType": "String", "StringValue": "six"}, + "seven": {"DataType": "String", "StringValue": "seven"}, + "eight": {"DataType": "String", "StringValue": "eight"}, + "nine": {"DataType": "String", "StringValue": "nine"}, + "ten": {"DataType": "String", "StringValue": "ten"}, + } + entries = [ + {"Id": "1", "Message": "ironmaiden", "MessageAttributes": message_attributes}, + {"Id": "2", "Message": "megadeth", "MessageAttributes": message_attributes}, + ] + sns.publish_batch(TopicArn=topic_arn, PublishBatchRequestEntries=entries) + self.get_spans() + + # get SNS messages via SQS + self.sqs_client.receive_message( + QueueUrl=self.sqs_test_queue["QueueUrl"], + MessageAttributeNames=["_datadog"], + WaitTimeSeconds=2, + ) + + # clean up resources + sns.delete_topic(TopicArn=topic_arn) + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_sns + @mock_sqs + def test_aws_payload_tagging_sns_valid_config(self): + with self.override_config( + "botocore", + dict( + payload_tagging_request="$..PublishBatchRequestEntries.[*].Message,$..PublishBatchRequestEntries.[*].Id", + payload_tagging_response="$..HTTPHeaders.*", + ), + ): + region = "us-east-1" + sns = self.session.create_client("sns", region_name=region, endpoint_url="http://localhost:4566") + + topic = sns.create_topic(Name="testTopic") + + topic_arn = topic["TopicArn"] + sqs_url = self.sqs_test_queue["QueueUrl"] + url_parts = sqs_url.split("/") + sqs_arn = "arn:aws:sqs:{}:{}:{}".format(region, url_parts[-2], url_parts[-1]) + sns.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint=sqs_arn) + + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(sns) + + message_attributes = { + "one": {"DataType": "String", "StringValue": "one"}, + "two": {"DataType": "String", "StringValue": "two"}, + "three": {"DataType": "String", "StringValue": "three"}, + "f.our": {"DataType": "String", "StringValue": "four"}, + "five": {"DataType": "String", "StringValue": "five"}, + "six": {"DataType": "String", "StringValue": "six"}, + "seven": {"DataType": "String", "StringValue": "seven"}, + "eight": {"DataType": "String", "StringValue": "eight"}, + "nine": {"DataType": "String", "StringValue": "nine"}, + "ten": {"DataType": "String", "StringValue": "ten"}, + } + entries = [ + {"Id": "1", "Message": "ironmaiden", "MessageAttributes": message_attributes}, + {"Id": "2", "Message": "megadeth", "MessageAttributes": message_attributes}, + ] + sns.publish_batch(TopicArn=topic_arn, PublishBatchRequestEntries=entries) + self.get_spans() + + # get SNS messages via SQS + self.sqs_client.receive_message( + QueueUrl=self.sqs_test_queue["QueueUrl"], + MessageAttributeNames=["_datadog"], + WaitTimeSeconds=2, + ) + + # clean up resources + sns.delete_topic(TopicArn=topic_arn) + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_s3 + def test_aws_payload_tagging_s3(self): + with self.override_config("botocore", dict(payload_tagging_request="all", payload_tagging_response="all")): + s3 = self.session.create_client("s3", region_name="us-west-2") + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(s3) + + s3.list_buckets() + s3.list_buckets() + + spans = self.get_spans() + assert spans + span = spans[0] + assert len(spans) == 2 + assert_is_measured(span) + assert span.get_tag("aws.operation") == "ListBuckets" + assert span.get_tag("component") == "botocore" + assert span.get_tag("span.kind"), "client" + assert_span_http_status_code(span, 200) + assert span.service == "test-botocore-tracing.s3" + assert span.resource == "s3.listbuckets" + + assert not span._links, "no links, i.e. no span pointers" + + # testing for span error + self.reset() + with pytest.raises(Exception): + s3.list_objects(bucket="mybucket") + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_s3 + def test_aws_payload_tagging_s3_invalid_config(self): + with self.override_config( + "botocore", + dict(payload_tagging_request="non_json_path", payload_tagging_response="$..Attr ibutes.PlatformCredential"), + ): + s3 = self.session.create_client("s3", region_name="us-west-2") + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(s3) + + s3.list_buckets() + s3.list_buckets() + + # testing for span error + self.reset() + with pytest.raises(Exception): + s3.list_objects(bucket="mybucket") + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_s3 + def test_aws_payload_tagging_s3_valid_config(self): + with self.override_config( + "botocore", dict(payload_tagging_request="$..bucket", payload_tagging_response="$..HTTPHeaders") + ): + s3 = self.session.create_client("s3", region_name="us-west-2") + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(s3) + + s3.list_buckets() + s3.list_buckets() + + # testing for span error + self.reset() + with pytest.raises(Exception): + s3.list_objects(bucket="mybucket") + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_events + def test_aws_payload_tagging_eventbridge(self): + with self.override_config("botocore", dict(payload_tagging_request="all", payload_tagging_response="all")): + bridge = self.session.create_client("events", region_name="us-east-1", endpoint_url="http://localhost:4566") + bridge.create_event_bus(Name="a-test-bus") + + entries = [ + { + "Source": "another-event-source", + "DetailType": "a-different-event-detail-type", + "Detail": json.dumps({"abc": "xyz"}), + "EventBusName": "a-test-bus", + }, + { + "Source": "some-event-source", + "DetailType": "some-event-detail-type", + "Detail": json.dumps({"foo": "bar"}), + "EventBusName": "a-test-bus", + }, + ] + bridge.put_rule( + Name="a-test-bus-rule", + EventBusName="a-test-bus", + EventPattern="""{"source": [{"prefix": ""}]}""", + State="ENABLED", + ) + + bridge.list_rules() + queue_url = self.sqs_test_queue["QueueUrl"] + bridge.put_targets( + Rule="a-test-bus-rule", + Targets=[{"Id": "a-test-bus-rule-target", "Arn": "arn:aws:sqs:us-east-1:000000000000:Test"}], + ) + + Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(bridge) + bridge.put_events(Entries=entries) + + self.sqs_client.receive_message(QueueUrl=queue_url, WaitTimeSeconds=2) + + bridge.delete_event_bus(Name="a-test-bus") + + @TracerTestCase.run_in_subprocess(env_overrides=dict()) + @pytest.mark.snapshot(ignores=snapshot_ignores) + @mock_kinesis + def test_aws_payload_tagging_kinesis(self): + with self.override_config("botocore", dict(payload_tagging_request="all", payload_tagging_response="all")): + client = self.session.create_client("kinesis", region_name="us-east-1") + stream_name = "test" + + partition_key = "1234" + data = [ + {"Data": json.dumps({"Hello": "World"}), "PartitionKey": partition_key}, + {"Data": json.dumps({"foo": "bar"}), "PartitionKey": partition_key}, + ] + + Pin.get_from(client).clone(tracer=self.tracer).onto(client) + + with self.tracer.trace("kinesis.manual_span"): + client.create_stream(StreamName=stream_name, ShardCount=1) + client.put_records(StreamName=stream_name, Records=data) diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json new file mode 100644 index 00000000000..4a9752f5e7b --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json @@ -0,0 +1,358 @@ +[[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.receivemessage", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219200000000", + "aws.agent": "botocore", + "aws.operation": "ReceiveMessage", + "aws.region": "us-east-1", + "aws.request.body.MessageAttributeNames.0": "_datadog", + "aws.request.body.QueueUrl": "http://localhost:4566/000000000000/Test", + "aws.request.body.WaitTimeSeconds": "2", + "aws.requestid": "16AU1RXT4RWPY1HXDGGNCCRF5GUXUVSG8R1Y8WTJYHHHBGPOXQK0", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "1577", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:44:34 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "16AU1RXT4RWPY1HXDGGNCCRF5GUXUVSG8R1Y8WTJYHHHBGPOXQK0", + "aws.response.body.RetryAttempts": "0", + "aws.sqs.queue_name": "Test", + "aws_account": "000000000000", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 1347317707, + "start": 1730486674014275278 + }], +[ + { + "name": "events.command", + "service": "test-botocore-tracing.events", + "resource": "events.deleteeventbus", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219300000000", + "aws.agent": "botocore", + "aws.operation": "DeleteEventBus", + "aws.region": "us-east-1", + "aws.requestid": "EVXSBTVJ91PNTZP5MRXLAMCQTJ8AM6OD93HW9Y71W0QTQQ8FKBY1", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "rulename": "a-test-bus", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 9324846, + "start": 1730486675361990473 + }], +[ + { + "name": "events.command", + "service": "test-botocore-tracing.events", + "resource": "events.putevents", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "PutEvents", + "aws.region": "us-east-1", + "aws.requestid": "60SMT4RD398SJX3QGMNKLNJWVJ2QO0PR21XZPXIUFDOLFU026ZIF", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "rulename": "", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 148280179, + "start": 1730486673839166140 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "AIJI264DD6Q3YCHTEZTMNT0J9H7XHWUHB0ZUCYQNA1MFIB3B6M29", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 9381944, + "start": 1730486673072395949 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "9CR6ZK55HZEO5DH880VV6MQM1VBIMWOY6TVI8X0UNC1IEFETHG33", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 7090898, + "start": 1730486673082518808 + }], +[ + { + "name": "events.command", + "service": "aws.events", + "resource": "events.createeventbus", + "trace_id": 5, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "CreateEventBus", + "aws.region": "us-east-1", + "aws.requestid": "3JM3A50OTY7PFQ6JJJ1O1YJWZ3D17LS1ZDL4NML7J8FKUEOTTYSI", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "rulename": "a-test-bus", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 698383864, + "start": 1730486673115255701 + }], +[ + { + "name": "events.command", + "service": "aws.events", + "resource": "events.putrule", + "trace_id": 6, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "PutRule", + "aws.region": "us-east-1", + "aws.requestid": "KNF5UFDRA15DP2B69OWT2144PWDEEGTL1TPD5JCY0VJR82J4T0O0", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "rulename": "a-test-bus-rule", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 7914310, + "start": 1730486673814005493 + }], +[ + { + "name": "events.command", + "service": "aws.events", + "resource": "events.listrules", + "trace_id": 7, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "ListRules", + "aws.region": "us-east-1", + "aws.requestid": "RSVB2I8YU33IJJTMR0OJFDC6PTNJ4YN70PBALOMPO4CEIUVO40RM", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 8146264, + "start": 1730486673822260931 + }], +[ + { + "name": "events.command", + "service": "aws.events", + "resource": "events.puttargets", + "trace_id": 8, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725219100000000", + "aws.agent": "botocore", + "aws.operation": "PutTargets", + "aws.region": "us-east-1", + "aws.requestid": "B9WMS96T4C4CO40IRKN8NE6RKATC8FU7W5I9EQNE71WQZE7S3ZD2", + "aws_service": "events", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "rulename": "", + "runtime-id": "45abaca6e35b47d4bbcab4d19d47895a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 5659, + "retry_attempts": 0 + }, + "duration": 7987546, + "start": 1730486673830745107 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json new file mode 100644 index 00000000000..5747ae5cb7f --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json @@ -0,0 +1,176 @@ +[[ + { + "name": "kinesis.manual_span", + "service": "", + "resource": "kinesis.manual_span", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6725212f00000000", + "language": "python", + "runtime-id": "2139211528f64010b06b4e04ea9d4202" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 4315 + }, + "duration": 2128653547, + "start": 1730486575665558320 + }, + { + "name": "kinesis.command", + "service": "aws.kinesis", + "resource": "kinesis.createstream", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "aws.agent": "botocore", + "aws.kinesis.stream_name": "test", + "aws.operation": "CreateStream", + "aws.region": "us-east-1", + "aws.request.body.ShardCount": "1", + "aws.request.body.StreamName": "test", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:42:56 GMT", + "aws.response.body.HTTPHeaders.server": "amazon.com", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RetryAttempts": "0", + "aws_service": "kinesis", + "component": "botocore", + "http.status_code": "200", + "region": "us-east-1", + "span.kind": "client", + "streamname": "test" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "retry_attempts": 0 + }, + "duration": 1222098056, + "start": 1730486575665790837 + }, + { + "name": "kinesis.command", + "service": "aws.kinesis", + "resource": "kinesis.putrecords", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "aws.agent": "botocore", + "aws.kinesis.stream_name": "test", + "aws.operation": "PutRecords", + "aws.region": "us-east-1", + "aws.request.body.Records.0.Data.Hello": "World", + "aws.request.body.Records.0.PartitionKey": "1234", + "aws.request.body.Records.1.Data.foo": "bar", + "aws.request.body.Records.1.PartitionKey": "1234", + "aws.request.body.StreamName": "test", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:42:57 GMT", + "aws.response.body.HTTPHeaders.server": "amazon.com", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RetryAttempts": "0", + "aws_service": "kinesis", + "component": "botocore", + "http.status_code": "200", + "region": "us-east-1", + "span.kind": "client", + "streamname": "test" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "retry_attempts": 0 + }, + "duration": 905954737, + "start": 1730486576888145627 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725212f00000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "S0Y391B6GM02XQHP7AAXP8BV0IZ0PTOUQY82NR7SHN651ERF2H8W", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "2139211528f64010b06b4e04ea9d4202", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 4315, + "retry_attempts": 0 + }, + "duration": 9310053, + "start": 1730486575625781470 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725212f00000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "HZLL7XGXSPWJD740TO4PBM4MN8CCVOL1UJRHUSV05ANLZCC0OEHN", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "2139211528f64010b06b4e04ea9d4202", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 4315, + "retry_attempts": 0 + }, + "duration": 6980220, + "start": 1730486575636068298 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json new file mode 100644 index 00000000000..123984f0989 --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json @@ -0,0 +1,196 @@ +[[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listobjects", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "67251ffe00000000", + "aws.agent": "botocore", + "aws.operation": "ListObjects", + "aws.region": "us-west-2", + "aws.request.body.bucket": "mybucket", + "aws_service": "s3", + "component": "botocore", + "error.message": "Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes", + "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/internal/botocore/patch.py\", line 253, in patched_api_call_fallback\n result = original_func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 962, in _make_api_call\n request_dict = self._convert_to_request_dict(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 1036, in _convert_to_request_dict\n request_dict = self._serializer.serialize_to_request(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/validate.py\", line 381, in serialize_to_request\n raise ParamValidationError(report=report.generate_report())\nbotocore.exceptions.ParamValidationError: Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes\n", + "error.type": "botocore.exceptions.ParamValidationError", + "language": "python", + "region": "us-west-2", + "runtime-id": "010e1ba41dc54c378179fd51445afc77", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 3979 + }, + "duration": 457826385, + "start": 1730486270132291421 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "67251ffc00000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "niWe5HO1MgcT2vq21MtdLzhY2xGs3kiyEzq7yR8ShXhvU6qQLAKa", + "aws.response.body.HTTPHeaders.x-amzn-requestid": "niWe5HO1MgcT2vq21MtdLzhY2xGs3kiyEzq7yR8ShXhvU6qQLAKa", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "niWe5HO1MgcT2vq21MtdLzhY2xGs3kiyEzq7yR8ShXhvU6qQLAKa", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "010e1ba41dc54c378179fd51445afc77", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 3979, + "retry_attempts": 0 + }, + "duration": 943066360, + "start": 1730486268691244191 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "67251ffd00000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "JaXNUT8bsHmE8bhkTRBiExKb1QWSI4qU3TnOHrfJX102cbxUDJ6S", + "aws.response.body.HTTPHeaders.x-amzn-requestid": "JaXNUT8bsHmE8bhkTRBiExKb1QWSI4qU3TnOHrfJX102cbxUDJ6S", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "JaXNUT8bsHmE8bhkTRBiExKb1QWSI4qU3TnOHrfJX102cbxUDJ6S", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "010e1ba41dc54c378179fd51445afc77", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 3979, + "retry_attempts": 0 + }, + "duration": 493884956, + "start": 1730486269635719446 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "67251ffc00000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "BSU0QP7O8U7LB7A9GTEKB4PZBU80TJP477RS4QH8O4NT83DY5ZV2", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "010e1ba41dc54c378179fd51445afc77", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 3979, + "retry_attempts": 0 + }, + "duration": 19671972, + "start": 1730486268555991113 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "67251ffc00000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "XYUX43DOF9DK8T4FVR9ANS4XBO6F7T25X5WR6UUNPIR9JBA1EYH5", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "010e1ba41dc54c378179fd51445afc77", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 3979, + "retry_attempts": 0 + }, + "duration": 25330622, + "start": 1730486268578039557 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json new file mode 100644 index 00000000000..12d47eb2b2e --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json @@ -0,0 +1,196 @@ +[[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listobjects", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce68500000000", + "aws.agent": "botocore", + "aws.operation": "ListObjects", + "aws.region": "us-west-2", + "aws.request.body.bucket": "mybucket", + "aws_service": "s3", + "component": "botocore", + "error.message": "Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes", + "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/internal/botocore/patch.py\", line 260, in patched_api_call_fallback\n result = original_func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 962, in _make_api_call\n request_dict = self._convert_to_request_dict(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 1036, in _convert_to_request_dict\n request_dict = self._serializer.serialize_to_request(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/validate.py\", line 381, in serialize_to_request\n raise ParamValidationError(report=report.generate_report())\nbotocore.exceptions.ParamValidationError: Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes\n", + "error.type": "botocore.exceptions.ParamValidationError", + "language": "python", + "region": "us-west-2", + "runtime-id": "04e00e3356e2405a80487efdd62ed083", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15909 + }, + "duration": 258360186, + "start": 1730995845585860675 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce68400000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "WH66MOGDZOAHLB9IDH1YENVWQWHNIFBCEYY6WXS90XPY62O5G9VQ", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "04e00e3356e2405a80487efdd62ed083", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15909, + "retry_attempts": 0 + }, + "duration": 10536457, + "start": 1730995844726668252 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce68400000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "Y3NGMJ72PNIQFJYS5Q7ZBP1WOQOS66HXSC9EVWZV6S0FT38QGYK4", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "04e00e3356e2405a80487efdd62ed083", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15909, + "retry_attempts": 0 + }, + "duration": 8369723, + "start": 1730995844738366514 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce68400000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "QKK7SrxEnTcIlaipjYlzX0PtU7L57aCZZDk0FeqDwc1n5o2kTHch", + "aws.response.body.HTTPHeaders.x-amzn-requestid": "QKK7SrxEnTcIlaipjYlzX0PtU7L57aCZZDk0FeqDwc1n5o2kTHch", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "QKK7SrxEnTcIlaipjYlzX0PtU7L57aCZZDk0FeqDwc1n5o2kTHch", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "04e00e3356e2405a80487efdd62ed083", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15909, + "retry_attempts": 0 + }, + "duration": 511793254, + "start": 1730995844812363498 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce68500000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "6K6tOztZCjgQlL0jLXtNBRrRYUlYdzY28c5oBJ68sPm8V2puTSf1", + "aws.response.body.HTTPHeaders.x-amzn-requestid": "6K6tOztZCjgQlL0jLXtNBRrRYUlYdzY28c5oBJ68sPm8V2puTSf1", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "6K6tOztZCjgQlL0jLXtNBRrRYUlYdzY28c5oBJ68sPm8V2puTSf1", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "04e00e3356e2405a80487efdd62ed083", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15909, + "retry_attempts": 0 + }, + "duration": 258140931, + "start": 1730995845324937218 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json new file mode 100644 index 00000000000..e33bc31123e --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json @@ -0,0 +1,196 @@ +[[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listobjects", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 1, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce67a00000000", + "aws.agent": "botocore", + "aws.operation": "ListObjects", + "aws.region": "us-west-2", + "aws.request.body.bucket": "redacted", + "aws_service": "s3", + "component": "botocore", + "error.message": "Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes", + "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/internal/botocore/patch.py\", line 260, in patched_api_call_fallback\n result = original_func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 962, in _make_api_call\n request_dict = self._convert_to_request_dict(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/client.py\", line 1036, in _convert_to_request_dict\n request_dict = self._serializer.serialize_to_request(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/root/project/.riot/venv_py3119_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]50_pytest-randomly_vcrpy601_botocore13449_boto313449/lib/python3.11/site-packages/botocore/validate.py\", line 381, in serialize_to_request\n raise ParamValidationError(report=report.generate_report())\nbotocore.exceptions.ParamValidationError: Parameter validation failed:\nMissing required parameter in input: \"Bucket\"\nUnknown parameter in input: \"bucket\", must be one of: Bucket, Delimiter, EncodingType, Marker, MaxKeys, Prefix, RequestPayer, ExpectedBucketOwner, OptionalObjectAttributes\n", + "error.type": "botocore.exceptions.ParamValidationError", + "language": "python", + "region": "us-west-2", + "runtime-id": "759db61f971e4e51b3876c2b0771e2b6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15573 + }, + "duration": 425924882, + "start": 1730995834381654112 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce67900000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "B7mZ00jPCOhjiOfnfZFCXJR1vlFYkR6uX51Sme72vnNfFzaHVB7D", + "aws.response.body.HTTPHeaders": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "B7mZ00jPCOhjiOfnfZFCXJR1vlFYkR6uX51Sme72vnNfFzaHVB7D", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "759db61f971e4e51b3876c2b0771e2b6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15573, + "retry_attempts": 0 + }, + "duration": 711895059, + "start": 1730995833210793741 + }], +[ + { + "name": "s3.command", + "service": "test-botocore-tracing.s3", + "resource": "s3.listbuckets", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce67900000000", + "aws.agent": "botocore", + "aws.operation": "ListBuckets", + "aws.region": "us-west-2", + "aws.requestid": "6rKSkfcYHp8uqW54aYbfS9f5l3nDFA2Xw0dgU65FZF6zAtCQLZa3", + "aws.response.body.HTTPHeaders": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "6rKSkfcYHp8uqW54aYbfS9f5l3nDFA2Xw0dgU65FZF6zAtCQLZa3", + "aws.response.body.RetryAttempts": "0", + "aws_service": "s3", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-west-2", + "runtime-id": "759db61f971e4e51b3876c2b0771e2b6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15573, + "retry_attempts": 0 + }, + "duration": 454908141, + "start": 1730995833924042857 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce67900000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "UZPT9ML68W82KW95PYMN61QF3HRNBY44SH0V1PPSG8W3QH1FL7OX", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "759db61f971e4e51b3876c2b0771e2b6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15573, + "retry_attempts": 0 + }, + "duration": 12420784, + "start": 1730995833133328246 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce67900000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "PUCYX1ZISWAFZSMWH7WZ5YZXJIM7PGIN2WHWKSSNEZUDYYCX6ZWW", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "759db61f971e4e51b3876c2b0771e2b6", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 15573, + "retry_attempts": 0 + }, + "duration": 8048502, + "start": 1730995833146606461 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json new file mode 100644 index 00000000000..25f05615402 --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json @@ -0,0 +1,386 @@ +[[ + { + "name": "sns.command", + "service": "test-botocore-tracing.sns", + "resource": "sns.deletetopic", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229500000000", + "aws.agent": "botocore", + "aws.operation": "DeleteTopic", + "aws.region": "us-east-1", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "Q2KSL71R5UZ98W8K82EI1N5K6NU6JQW1A4ELBFTWNT56EQ1YP460", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "243", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:48:53 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "Q2KSL71R5UZ98W8K82EI1N5K6NU6JQW1A4ELBFTWNT56EQ1YP460", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 0.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 914034299, + "start": 1730486933261601823 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.receivemessage", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229400000000", + "aws.agent": "botocore", + "aws.operation": "ReceiveMessage", + "aws.region": "us-east-1", + "aws.request.body.MessageAttributeNames.0": "_datadog", + "aws.request.body.QueueUrl": "http://localhost:4566/000000000000/Test", + "aws.request.body.WaitTimeSeconds": "2", + "aws.requestid": "NKWPYHQNVPLJRXAAPSDUJJ9NCY5AIQZZE7E9HJZX9NCWDDCWUFZX", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "2342", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:48:52 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "NKWPYHQNVPLJRXAAPSDUJJ9NCY5AIQZZE7E9HJZX9NCWDDCWUFZX", + "aws.response.body.RetryAttempts": "0", + "aws.sqs.queue_name": "Test", + "aws_account": "000000000000", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 877443506, + "start": 1730486932383733772 + }], +[ + { + "name": "sns.command", + "service": "test-botocore-tracing.sns", + "resource": "sns.publishbatch", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229300000000", + "aws.agent": "botocore", + "aws.operation": "PublishBatch", + "aws.region": "us-east-1", + "aws.request.body.PublishBatchRequestEntries.0.Id": "1", + "aws.request.body.PublishBatchRequestEntries.0.Message": "ironmaiden", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.eight.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.eight.StringValue": "eight", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.five.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.five.StringValue": "five", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.four.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.four.StringValue": "four", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.nine.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.nine.StringValue": "nine", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.one.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.one.StringValue": "one", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.seven.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.seven.StringValue": "seven", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.six.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.six.StringValue": "six", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.ten.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.ten.StringValue": "ten", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.three.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.three.StringValue": "three", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.two.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.two.StringValue": "two", + "aws.request.body.PublishBatchRequestEntries.1.Id": "2", + "aws.request.body.PublishBatchRequestEntries.1.Message": "megadeth", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.eight.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.eight.StringValue": "eight", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.five.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.five.StringValue": "five", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.four.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.four.StringValue": "four", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.nine.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.nine.StringValue": "nine", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.one.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.one.StringValue": "one", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.seven.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.seven.StringValue": "seven", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.six.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.six.StringValue": "six", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.ten.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.ten.StringValue": "ten", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.three.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.three.StringValue": "three", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.two.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.two.StringValue": "two", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "TNF9YUHZNEE2DH09T351ETMYYBHDO7IVF6AHLDHWCTGW8N1NALS9", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "493", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:48:51 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "TNF9YUHZNEE2DH09T351ETMYYBHDO7IVF6AHLDHWCTGW8N1NALS9", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 930994951, + "start": 1730486931437714538 + }], +[ + { + "name": "sns.command", + "service": "aws.sns", + "resource": "sns.createtopic", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229100000000", + "aws.agent": "botocore", + "aws.operation": "CreateTopic", + "aws.region": "us-east-1", + "aws.request.body.Name": "testTopic", + "aws.requestid": "2SP1AKIYVI5YTSKQBPCT46FFRB3EHSPEF9HI80FKC5BOITPMG21L", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "347", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:48:50 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "2SP1AKIYVI5YTSKQBPCT46FFRB3EHSPEF9HI80FKC5BOITPMG21L", + "aws.response.body.RetryAttempts": "0", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 1156027576, + "start": 1730486929411636687 + }], +[ + { + "name": "sns.command", + "service": "aws.sns", + "resource": "sns.subscribe", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229200000000", + "aws.agent": "botocore", + "aws.operation": "Subscribe", + "aws.region": "us-east-1", + "aws.request.body.Endpoint": "redacted", + "aws.request.body.Protocol": "sqs", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "DRZH8BAT8J1JR5XVGQ39DH5R0UC7T0SFOAP4EG28LXF9T0B38UIL", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "390", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:48:50 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "DRZH8BAT8J1JR5XVGQ39DH5R0UC7T0SFOAP4EG28LXF9T0B38UIL", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 869311702, + "start": 1730486930568014008 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 5, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229100000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "OZZ8QUD0LZ2VTC35GMOFDAOEC1QYXTHUXCOHQDE3FSHVTUIBPOC9", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 9532531, + "start": 1730486929363170092 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 6, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "6725229100000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "VICJ8RVZKMBFGEHY06P9E28AOEZKCU5R66AE00RSB229D8Z8Y8TR", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "7fd35701caac4cdcaad996ae75408bfc", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 8683, + "retry_attempts": 0 + }, + "duration": 7289635, + "start": 1730486929373616666 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json new file mode 100644 index 00000000000..095f8179618 --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json @@ -0,0 +1,386 @@ +[[ + { + "name": "sns.command", + "service": "test-botocore-tracing.sns", + "resource": "sns.deletetopic", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8ac00000000", + "aws.agent": "botocore", + "aws.operation": "DeleteTopic", + "aws.region": "us-east-1", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "JVBHL3D33EU9ESFUBT6UKMVYQZSIF8BUYJOUNISNJE7722YAZCXZ", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "redacted", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "redacted", + "aws.response.body.HTTPHeaders.connection": "redacted", + "aws.response.body.HTTPHeaders.content-length": "redacted", + "aws.response.body.HTTPHeaders.content-type": "redacted", + "aws.response.body.HTTPHeaders.date": "redacted", + "aws.response.body.HTTPHeaders.server": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "JVBHL3D33EU9ESFUBT6UKMVYQZSIF8BUYJOUNISNJE7722YAZCXZ", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 0.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 950339368, + "start": 1730996396594477506 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.receivemessage", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8ab00000000", + "aws.agent": "botocore", + "aws.operation": "ReceiveMessage", + "aws.region": "us-east-1", + "aws.request.body.MessageAttributeNames.0": "_datadog", + "aws.request.body.QueueUrl": "http://localhost:4566/000000000000/Test", + "aws.request.body.WaitTimeSeconds": "2", + "aws.requestid": "MIWENHGLXX91BMDPEB6MCEP2SKAJ75EERGWKOZIMLK73DBIP2Y2M", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "redacted", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "redacted", + "aws.response.body.HTTPHeaders.connection": "redacted", + "aws.response.body.HTTPHeaders.content-length": "redacted", + "aws.response.body.HTTPHeaders.content-type": "redacted", + "aws.response.body.HTTPHeaders.date": "redacted", + "aws.response.body.HTTPHeaders.server": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "MIWENHGLXX91BMDPEB6MCEP2SKAJ75EERGWKOZIMLK73DBIP2Y2M", + "aws.response.body.RetryAttempts": "0", + "aws.sqs.queue_name": "Test", + "aws_account": "000000000000", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 936840871, + "start": 1730996395657282340 + }], +[ + { + "name": "sns.command", + "service": "test-botocore-tracing.sns", + "resource": "sns.publishbatch", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8aa00000000", + "aws.agent": "botocore", + "aws.operation": "PublishBatch", + "aws.region": "us-east-1", + "aws.request.body.PublishBatchRequestEntries.0.Id": "redacted", + "aws.request.body.PublishBatchRequestEntries.0.Message": "redacted", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.eight.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.eight.StringValue": "eight", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.f\\.our.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.f\\.our.StringValue": "four", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.five.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.five.StringValue": "five", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.nine.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.nine.StringValue": "nine", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.one.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.one.StringValue": "one", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.seven.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.seven.StringValue": "seven", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.six.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.six.StringValue": "six", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.ten.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.ten.StringValue": "ten", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.three.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.three.StringValue": "three", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.two.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.0.MessageAttributes.two.StringValue": "two", + "aws.request.body.PublishBatchRequestEntries.1.Id": "redacted", + "aws.request.body.PublishBatchRequestEntries.1.Message": "redacted", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.eight.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.eight.StringValue": "eight", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.f\\.our.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.f\\.our.StringValue": "four", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.five.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.five.StringValue": "five", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.nine.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.nine.StringValue": "nine", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.one.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.one.StringValue": "one", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.seven.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.seven.StringValue": "seven", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.six.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.six.StringValue": "six", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.ten.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.ten.StringValue": "ten", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.three.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.three.StringValue": "three", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.two.DataType": "String", + "aws.request.body.PublishBatchRequestEntries.1.MessageAttributes.two.StringValue": "two", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "O6X3QA4Q41MYQ6XSGNDZCLA0PSKUKJ045M1LZW3JROSYD5XUFDUT", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "redacted", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "redacted", + "aws.response.body.HTTPHeaders.connection": "redacted", + "aws.response.body.HTTPHeaders.content-length": "redacted", + "aws.response.body.HTTPHeaders.content-type": "redacted", + "aws.response.body.HTTPHeaders.date": "redacted", + "aws.response.body.HTTPHeaders.server": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "O6X3QA4Q41MYQ6XSGNDZCLA0PSKUKJ045M1LZW3JROSYD5XUFDUT", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 947785220, + "start": 1730996394696414263 + }], +[ + { + "name": "sns.command", + "service": "aws.sns", + "resource": "sns.createtopic", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8a800000000", + "aws.agent": "botocore", + "aws.operation": "CreateTopic", + "aws.region": "us-east-1", + "aws.request.body.Name": "testTopic", + "aws.requestid": "AMFHK8CWR6R3KC6JT993EINBSHRUAKJ0K8DDMQPFSKVG83GGCZFM", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "redacted", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "redacted", + "aws.response.body.HTTPHeaders.connection": "redacted", + "aws.response.body.HTTPHeaders.content-length": "redacted", + "aws.response.body.HTTPHeaders.content-type": "redacted", + "aws.response.body.HTTPHeaders.date": "redacted", + "aws.response.body.HTTPHeaders.server": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "AMFHK8CWR6R3KC6JT993EINBSHRUAKJ0K8DDMQPFSKVG83GGCZFM", + "aws.response.body.RetryAttempts": "0", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 1002298135, + "start": 1730996392768290024 + }], +[ + { + "name": "sns.command", + "service": "aws.sns", + "resource": "sns.subscribe", + "trace_id": 4, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8a900000000", + "aws.agent": "botocore", + "aws.operation": "Subscribe", + "aws.region": "us-east-1", + "aws.request.body.Endpoint": "redacted", + "aws.request.body.Protocol": "sqs", + "aws.request.body.TopicArn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws.requestid": "4CICZ8EQUUOACM1IORQNZEDQ33KF9L1RN77E4HL844IK3HR67905", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "redacted", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "redacted", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "redacted", + "aws.response.body.HTTPHeaders.connection": "redacted", + "aws.response.body.HTTPHeaders.content-length": "redacted", + "aws.response.body.HTTPHeaders.content-type": "redacted", + "aws.response.body.HTTPHeaders.date": "redacted", + "aws.response.body.HTTPHeaders.server": "redacted", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "4CICZ8EQUUOACM1IORQNZEDQ33KF9L1RN77E4HL844IK3HR67905", + "aws.response.body.RetryAttempts": "0", + "aws.sns.topic_arn": "arn:aws:sns:us-east-1:000000000000:testTopic", + "aws_account": "000000000000", + "aws_service": "sns", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client", + "topicname": "testTopic" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 925091785, + "start": 1730996393770937800 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 5, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8a800000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "1R7ANT6AC71E49ONC2STF3NHNM8YGOWXDNYGBPX1QGLXEB6MXAZ3", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 9878649, + "start": 1730996392724547367 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 6, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672ce8a800000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "I4Y5CAECJIZRS8C7GVJK7GQG0Y1NDGA95JPTBSWNCXIQGRNIOZS3", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "cb8f28ac358e4a2a93617272fb46ba0a", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 17253, + "retry_attempts": 0 + }, + "duration": 7524614, + "start": 1730996392735210004 + }]] diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json new file mode 100644 index 00000000000..af0f6cc4ee0 --- /dev/null +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json @@ -0,0 +1,205 @@ +[[ + { + "name": "sqs.command", + "service": "test-botocore-tracing.sqs", + "resource": "sqs.receivemessage", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672522b300000000", + "aws.agent": "botocore", + "aws.operation": "ReceiveMessage", + "aws.region": "us-east-1", + "aws.request.body.MessageAttributeNames.0": "_datadog", + "aws.request.body.QueueUrl": "http://localhost:4566/000000000000/Test", + "aws.request.body.WaitTimeSeconds": "2", + "aws.requestid": "WJ6C1QMRXCMYSO918PTT0Y8488VH2RZHPKHZZXQZ1E3L079PE2PE", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "654", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:49:24 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "WJ6C1QMRXCMYSO918PTT0Y8488VH2RZHPKHZZXQZ1E3L079PE2PE", + "aws.response.body.RetryAttempts": "0", + "aws.sqs.queue_name": "Test", + "aws_account": "000000000000", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "e45a6c451bfb47bfbb0f4f36bd0803a3", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 9691, + "retry_attempts": 0 + }, + "duration": 895390012, + "start": 1730486964009311666 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.listqueues", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672522b200000000", + "aws.agent": "botocore", + "aws.operation": "ListQueues", + "aws.region": "us-east-1", + "aws.requestid": "GCXA1QNSIT8ZCO2ER8SPI6U6QPTINWZKK1J0QLJE4FV89SOAG7VY", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "region": "us-east-1", + "runtime-id": "e45a6c451bfb47bfbb0f4f36bd0803a3", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 9691, + "retry_attempts": 0 + }, + "duration": 9492759, + "start": 1730486963003404555 + }], +[ + { + "name": "sqs.command", + "service": "aws.sqs", + "resource": "sqs.createqueue", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672522b300000000", + "aws.agent": "botocore", + "aws.operation": "CreateQueue", + "aws.region": "us-east-1", + "aws.requestid": "A3R2CT5ZWO1XH9GEAT7L232IPRFJSCTBRRGO9LCSBEUX0T8KHC1O", + "aws.sqs.queue_name": "Test", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "e45a6c451bfb47bfbb0f4f36bd0803a3", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 9691, + "retry_attempts": 0 + }, + "duration": 7553649, + "start": 1730486963013740697 + }], +[ + { + "name": "sqs.command", + "service": "test-botocore-tracing.sqs", + "resource": "sqs.sendmessage", + "trace_id": 3, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "672522b300000000", + "aws.agent": "botocore", + "aws.operation": "SendMessage", + "aws.region": "us-east-1", + "aws.request.body.MessageAttributes.eight.DataType": "String", + "aws.request.body.MessageAttributes.eight.StringValue": "eight", + "aws.request.body.MessageAttributes.five.DataType": "String", + "aws.request.body.MessageAttributes.five.StringValue": "five", + "aws.request.body.MessageAttributes.four.DataType": "String", + "aws.request.body.MessageAttributes.four.StringValue": "four", + "aws.request.body.MessageAttributes.nine.DataType": "String", + "aws.request.body.MessageAttributes.nine.StringValue": "nine", + "aws.request.body.MessageAttributes.one.DataType": "String", + "aws.request.body.MessageAttributes.one.StringValue": "one", + "aws.request.body.MessageAttributes.seven.DataType": "String", + "aws.request.body.MessageAttributes.seven.StringValue": "seven", + "aws.request.body.MessageAttributes.six.DataType": "String", + "aws.request.body.MessageAttributes.six.StringValue": "six", + "aws.request.body.MessageAttributes.ten.DataType": "String", + "aws.request.body.MessageAttributes.ten.StringValue": "ten", + "aws.request.body.MessageAttributes.three.DataType": "String", + "aws.request.body.MessageAttributes.three.StringValue": "three", + "aws.request.body.MessageAttributes.two.DataType": "String", + "aws.request.body.MessageAttributes.two.StringValue": "two", + "aws.request.body.MessageBody": "world", + "aws.request.body.QueueUrl": "http://localhost:4566/000000000000/Test", + "aws.requestid": "NLK4TDG9YGTYRAMQ6QHN6NWC6QKRSSIZ8IBF0DS4RTY2Y3YWA7XC", + "aws.response.body.HTTPHeaders.access-control-allow-headers": "authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request", + "aws.response.body.HTTPHeaders.access-control-allow-methods": "HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH", + "aws.response.body.HTTPHeaders.access-control-allow-origin": "*", + "aws.response.body.HTTPHeaders.access-control-expose-headers": "etag,x-amz-version-id", + "aws.response.body.HTTPHeaders.connection": "close", + "aws.response.body.HTTPHeaders.content-length": "493", + "aws.response.body.HTTPHeaders.content-type": "text/xml", + "aws.response.body.HTTPHeaders.date": "Fri, 01 Nov 2024 18:49:23 GMT", + "aws.response.body.HTTPHeaders.server": "hypercorn-h11", + "aws.response.body.HTTPStatusCode": "200", + "aws.response.body.RequestId": "NLK4TDG9YGTYRAMQ6QHN6NWC6QKRSSIZ8IBF0DS4RTY2Y3YWA7XC", + "aws.response.body.RetryAttempts": "0", + "aws.sqs.queue_name": "Test", + "aws_account": "000000000000", + "aws_service": "sqs", + "component": "botocore", + "http.status_code": "200", + "language": "python", + "queuename": "Test", + "region": "us-east-1", + "runtime-id": "e45a6c451bfb47bfbb0f4f36bd0803a3", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 9691, + "retry_attempts": 0 + }, + "duration": 964509306, + "start": 1730486963031752521 + }]] From b182d74d4576a6702a8f2bec38990210c06ebf52 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Wed, 13 Nov 2024 08:57:59 +0000 Subject: [PATCH 143/372] chore(ci_visibility): fix telemetry name for manual api events (#11359) The correct metric name is `dd.instrumentation_telemetry_data.civisibility.manual_api_events` . ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/telemetry/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/internal/ci_visibility/telemetry/events.py b/ddtrace/internal/ci_visibility/telemetry/events.py index fb586a4cc96..189c9a838c2 100644 --- a/ddtrace/internal/ci_visibility/telemetry/events.py +++ b/ddtrace/internal/ci_visibility/telemetry/events.py @@ -16,7 +16,7 @@ class EVENTS_TELEMETRY(str, Enum): CREATED = "event_created" FINISHED = "event_finished" - MANUAL_API_EVENT = "manual_api_event" + MANUAL_API_EVENT = "manual_api_events" ENQUEUED_FOR_SERIALIZATION = "events_enqueued_for_serialization" From 39351c698518e8a3d9a5e9583c025c9269ec078f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20De=20Ara=C3=BAjo?= Date: Wed, 13 Nov 2024 08:58:20 +0000 Subject: [PATCH 144/372] chore(ci_visibility): fix itr_skipped tag in settings_response telemetry (#11362) We are currently setting the same telemetry tag to indicate that ITR is enabled and to indicate that it's actually skipping tests. This PR fixes this. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> --- ddtrace/internal/ci_visibility/telemetry/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/internal/ci_visibility/telemetry/git.py b/ddtrace/internal/ci_visibility/telemetry/git.py index 000ddfb2249..fa4b2e87285 100644 --- a/ddtrace/internal/ci_visibility/telemetry/git.py +++ b/ddtrace/internal/ci_visibility/telemetry/git.py @@ -69,7 +69,7 @@ def record_settings_response( if require_git: response_tags.append(("require_git", "1")) if itr_enabled: - response_tags.append(("itrskip_enabled", "1")) + response_tags.append(("itr_enabled", "1")) if early_flake_detection_enabled: response_tags.append(("early_flake_detection_enabled", "1")) From 48e895244c935c02c88ee14a5eba13eec41ff9cb Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 13 Nov 2024 09:25:37 -0500 Subject: [PATCH 145/372] fix(lib-injection): fix guardrail check when sys.argv is not available (#11364) --- lib-injection/sources/sitecustomize.py | 6 ++++++ .../notes/fix-lib-injection-sys-argv-fefc775a4a262d86.yaml | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 releasenotes/notes/fix-lib-injection-sys-argv-fefc775a4a262d86.yaml diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 6c9b38e6dca..3f32814a638 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -181,6 +181,12 @@ def package_is_compatible(package_name, package_version): def get_first_incompatible_sysarg(): + # bug: sys.argv is not always available in all python versions + # https://bugs.python.org/issue32573 + if not hasattr(sys, "argv"): + _log("sys.argv not available, skipping sys.argv check", level="debug") + return + _log(f"Checking sysargs: len(argv): {len(sys.argv)}", level="debug") if len(sys.argv) <= 1: return diff --git a/releasenotes/notes/fix-lib-injection-sys-argv-fefc775a4a262d86.yaml b/releasenotes/notes/fix-lib-injection-sys-argv-fefc775a4a262d86.yaml new file mode 100644 index 00000000000..ab61e914fb5 --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-sys-argv-fefc775a4a262d86.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: Fix injection guardrail check when ``sys.argv`` is not available. From b18a73c09de66b369329a7737659e18109be2b75 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Wed, 13 Nov 2024 10:59:37 -0500 Subject: [PATCH 146/372] chore: fix codespell errors in changelog (#11378) Fix two codespell errors in the changelog. These pre-existing errors were blocking new release note generation after enforcing stricter codespell checks. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17541db9926..97b9e1a0bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1493,7 +1493,7 @@ tracing: This release adds support for lazy sampling, essentially moving when we - profiler: Fixes a sigabrt when shutdown occurs during an upload -- otel: Ensures all otel sampling decisions are consistent with Datadog Spans. This prevents otel spans in a distrbuted trace from being sampled differently than Datadog spans in the same trace. +- otel: Ensures all otel sampling decisions are consistent with Datadog Spans. This prevents otel spans in a distributed trace from being sampled differently than Datadog spans in the same trace. - tracing: Fix an issue where remote configuration values would not be reverted when unset in the UI. @@ -5589,7 +5589,7 @@ If you are using the profiler, please note that `ddtrace.profile` has been renam - Check profiler accuracy (#1260) - chore(ci): fix flake8 and pin pytest and readme_renderer (#1278) - fix(tests,profile): do not test the number of locking events (#1282) -- fix: do not build wheels on Python 3.4 + run test buildin wheels in the CI (#1287) +- fix: do not build wheels on Python 3.4 + run test building wheels in the CI (#1287) - fix(tests,profiling): correct number of frames handling (#1290) - fix(tests, opentracer): flaky threading test (#1293) - build: use latest manylinux images (#1305) From 079e4789ccd86b1aa567238d525ba45c1cedce56 Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Wed, 13 Nov 2024 13:57:38 -0500 Subject: [PATCH 147/372] fix(profiling): reduce lock profiler log spam (#11377) The lock profiler would log a warning if it couldn't determine a name for a lock, and it would try determining a name multiple times for the same lock. This lead to excessive log spam. Downgrade this to a debug log and only try to determine the name once. --- ddtrace/profiling/collector/_lock.py | 4 ++-- ...ing-reduce-lock-profiler-log-spam-5931d957ff1becd4.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/profiling-reduce-lock-profiler-log-spam-5931d957ff1becd4.yaml diff --git a/ddtrace/profiling/collector/_lock.py b/ddtrace/profiling/collector/_lock.py index 74a94e380e2..f2d1289d73b 100644 --- a/ddtrace/profiling/collector/_lock.py +++ b/ddtrace/profiling/collector/_lock.py @@ -269,7 +269,7 @@ def _find_self_name(self, var_dict: typing.Dict): # Get lock acquire/release call location and variable name the lock is assigned to def _maybe_update_self_name(self): - if self._self_name: + if self._self_name is not None: return try: # We expect the call stack to be like this: @@ -298,7 +298,7 @@ def _maybe_update_self_name(self): if not self._self_name: self._self_name = "" - LOG.warning( + LOG.debug( "Failed to get lock variable name, we only support local/global variables and their attributes." ) diff --git a/releasenotes/notes/profiling-reduce-lock-profiler-log-spam-5931d957ff1becd4.yaml b/releasenotes/notes/profiling-reduce-lock-profiler-log-spam-5931d957ff1becd4.yaml new file mode 100644 index 00000000000..5a9fe3a174d --- /dev/null +++ b/releasenotes/notes/profiling-reduce-lock-profiler-log-spam-5931d957ff1becd4.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + profiling: The lock profiler would log a warning if it couldn't determine a + name for a lock, and it would try determining a name multiple times for + the same lock. This lead to excessive log spam. Downgrade this to a + debug log and only try to determine the name once. From 2c3ab58922c6b1029a57dbbfd8e6bcfead6a17ed Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 13 Nov 2024 14:08:24 -0500 Subject: [PATCH 148/372] chore(lib-injection): fix tracer_version reported with ssi telemetry (#11370) --- lib-injection/sources/sitecustomize.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 3f32814a638..4ad07f4c60e 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -48,6 +48,16 @@ def parse_version(version: str) -> Tuple: EXECUTABLE_DENY_LOCATION = os.path.abspath(os.path.join(SCRIPT_DIR, "denied_executables.txt")) +def get_oci_ddtrace_version(): + version_path = os.path.join(SCRIPT_DIR, "version") + try: + with open(version_path, "r") as version_file: + return version_file.read().strip() + except Exception: + _log("Failed to read version file %s" % (version_path,), level="debug") + return "unknown" + + def build_installed_pkgs(): installed_packages = {} if sys.version_info >= (3, 8): @@ -106,14 +116,14 @@ def create_count_metric(metric, tags=None): } -def gen_telemetry_payload(telemetry_events): +def gen_telemetry_payload(telemetry_events, ddtrace_version="unknown"): return { "metadata": { "language_name": "python", "language_version": PYTHON_VERSION, "runtime_name": PYTHON_RUNTIME, "runtime_version": PYTHON_VERSION, - "tracer_version": INSTALLED_PACKAGES.get("ddtrace", "unknown"), + "tracer_version": ddtrace_version, "pid": os.getpid(), }, "points": telemetry_events, @@ -203,6 +213,7 @@ def _inject(): global PYTHON_RUNTIME global PKGS_ALLOW_LIST global EXECUTABLES_DENY_LIST + DDTRACE_VERSION = get_oci_ddtrace_version() INSTALLED_PACKAGES = build_installed_pkgs() PYTHON_RUNTIME = platform.python_implementation().lower() PYTHON_VERSION = platform.python_version() @@ -297,7 +308,7 @@ def _inject(): ], ) ) - telemetry_event = gen_telemetry_payload(telemetry_data) + telemetry_event = gen_telemetry_payload(telemetry_data, DDTRACE_VERSION) send_telemetry(telemetry_event) return @@ -347,12 +358,14 @@ def _inject(): "injection_forced:" + str(runtime_incomp or integration_incomp).lower(), ], ) - ] + ], + DDTRACE_VERSION, ) send_telemetry(event) except Exception as e: event = gen_telemetry_payload( - [create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])] + [create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])], + DDTRACE_VERSION, ) send_telemetry(event) _log("failed to load ddtrace.bootstrap.sitecustomize: %s" % e, level="error") From 4f7052e35ecbfc2ef5939ce5dd661952cc276d40 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:01:58 -0500 Subject: [PATCH 149/372] chore(docs): organize configurations.rst (#11076) Organizing our configuration docs. We should eventually switch over to using Envier for all of these so they can be auto-generated and the raw .rst file is much more clean, but for now this will help with readability. No content was changed, just reshuffling of the ordering and some grouping. See the sample generated doc [here](https://ddtrace.readthedocs.io/en/erikayasuda-organize-configs/configuration.html) compared to what is published as [latest](https://ddtrace.readthedocs.io/en/latest/configuration.html). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/configuration.rst | 971 +++++++++++++++++++++++------------------ 1 file changed, 556 insertions(+), 415 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index c75997b548a..9fb0cb0a7ef 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -10,311 +10,273 @@ see specific integration documentation for more details. The following environment variables for the tracer are supported: +Unified Service Tagging +----------------------- .. ddtrace-configuration-options:: + DD_ENV: description: | Set an application's environment e.g. ``prod``, ``pre-prod``, ``staging``. Added in ``v0.36.0``. See `Unified Service Tagging`_ for more information. DD_SERVICE: default: (autodetected) + description: | Set the service name to be used for this application. A default is provided for these integrations: :ref:`bottle`, :ref:`flask`, :ref:`grpc`, :ref:`pyramid`, :ref:`tornado`, :ref:`celery`, :ref:`django` and :ref:`falcon`. Added in ``v0.36.0``. See `Unified Service Tagging`_ for more information. - DD_SERVICE_MAPPING: - description: | - Define service name mappings to allow renaming services in traces, e.g. ``postgres:postgresql,defaultdb:postgresql``. - - DD_TAGS: - description: | - Set global tags to be attached to every span. Value must be either comma or space separated. e.g. ``key1:value1,key2:value2`` or ``key1:value key2:value2``. - - If a tag value is not supplied the value will be an empty string. e.g. ``key1,key2`` or ``key1 key2``. - version_added: - v0.38.0: Comma separated support added - v0.48.0: Space separated support added - - DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED: - type: Boolean - default: False - description: | - Enables propagation of baggage items through http headers with prefix ``ot-baggage-``. - version_added: - v2.4.0: - DD_VERSION: description: | Set an application's version in traces and logs e.g. ``1.2.3``, ``6c44da20``, ``2020.02.13``. Generally set along with ``DD_SERVICE``. See `Unified Service Tagging`_ for more information. + version_added: v0.36.0: - DD_SITE: - default: datadoghq.com - description: | - Specify which site to use for uploading profiles and logs. Set to - ``datadoghq.eu`` to use EU site. +Traces +------ - DD_TRACE_ENABLED: - type: Boolean +.. ddtrace-configuration-options:: + DD__DISTRIBUTED_TRACING: default: True + description: | - Enable sending of spans to the Agent. Note that instrumentation will still be installed and spans will be - generated. + Enables distributed tracing for the specified . + version_added: - v0.41.0: | - Formerly named ``DATADOG_TRACE_ENABLED`` + v2.7.0: - DD_TRACE_OTEL_ENABLED: - type: Boolean + DD__SERVICE: + type: String + default: + + description: | + Set the service name, allowing default service name overrides for traces for the specific . + + version_added: + v2.11.0: + + DD_ASGI_TRACE_WEBSOCKET: default: False + description: | - When used with ``ddtrace-run`` this configuration enables OpenTelemetry support. To enable OpenTelemetry without `ddtrace-run` refer - to the following :mod:`docs `. + Enables tracing ASGI websockets. Please note that the websocket span duration will last until the + connection is closed, which can result in long running spans. + version_added: - v1.12.0: - - DD_RUNTIME_METRICS_ENABLED: + v2.7.0: + + DD_BOTOCORE_EMPTY_POLL_ENABLED: + type: Boolean + default: True + + description: | + Enables creation of consumer span when AWS SQS and AWS Kinesis ``poll()`` operations return no records. When disabled, no consumer span is created + if no records are returned. + + version_added: + v2.6.0: + + DD_BOTOCORE_PROPAGATION_ENABLED: + type: Boolean + default: False + + description: | + Enables trace context propagation connecting producer and consumer spans within a single trace for AWS SQS, SNS, and Kinesis messaging services. + + version_added: + v2.6.0: + + DD_HTTP_SERVER_TAG_QUERY_STRING: type: Boolean - default: False + default: True + description: Send query strings in http.url tag in http server integrations. + + DD_SERVICE_MAPPING: description: | - When used with ``ddtrace-run`` this configuration enables sending runtime metrics to Datadog. - These metrics track the memory management and concurrency of the python runtime. - Refer to the following `docs ` _ for more information. + Define service name mappings to allow renaming services in traces, e.g. ``postgres:postgresql,defaultdb:postgresql``. - DD_INSTRUMENTATION_TELEMETRY_ENABLED: + DD_TRACE__ENABLED: type: Boolean default: True + description: | - Enables sending :ref:`telemetry ` events to the agent. + Enables to be patched. For example, ``DD_TRACE_DJANGO_ENABLED=false`` will disable the Django + integration from being installed. + + version_added: + v0.41.0: DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: type: Boolean default: False + description: | This configuration enables the generation of 128 bit trace ids. + version_added: v1.12.0: - DD_TRACE_DEBUG: - type: Boolean - default: False + DD_TRACE_API_VERSION: + default: | + ``v0.5`` + description: | - Enables debug logging in the tracer. + The trace API version to use when sending traces to the Datadog agent. - Can be used with `DD_TRACE_LOG_FILE` to route logs to a file. + Currently, the supported versions are: ``v0.4`` and ``v0.5``. + version_added: - v0.41.0: | - Formerly named ``DATADOG_TRACE_DEBUG`` + v0.56.0: + v1.7.0: default changed to ``v0.5``. + v1.19.1: default reverted to ``v0.4``. + v2.4.0: default changed to ``v0.5``. - DD_TRACE_LOG_FILE_LEVEL: - default: DEBUG - description: | - Configures the ``RotatingFileHandler`` used by the `ddtrace` logger to write logs to a file based on the level specified. - Defaults to `DEBUG`, but will accept the values found in the standard **logging** library, such as WARNING, ERROR, and INFO, - if further customization is needed. Files are not written to unless ``DD_TRACE_LOG_FILE`` has been defined. + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH: + type: Integer + default: 10 + description: | + Sets the depth of expanding the JSON AWS payload after which we stop creating tags. + version_added: + v2.17.0: - DD_TRACE_LOG_FILE: - description: | - Directs `ddtrace` logs to a specific file. Note: The default backup count is 1. For larger logs, use with ``DD_TRACE_LOG_FILE_SIZE_BYTES``. - To fine tune the logging level, use with ``DD_TRACE_LOG_FILE_LEVEL``. + DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_TAGS: + type: Integer + default: 758 + description: | + Sets the the maximum number of tags that will be added when expanding an AWS payload. + version_added: + v2.17.0: - DD_TRACE_LOG_FILE_SIZE_BYTES: - type: Int - default: 15728640 - description: | - Max size for a file when used with `DD_TRACE_LOG_FILE`. When a log has exceeded this size, there will be one backup log file created. - In total, the files will store ``2 * DD_TRACE_LOG_FILE_SIZE_BYTES`` worth of logs. + DD_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES: + type: Set + default: {"s3", "sns", "sqs", "kinesis", "eventbridge"} + description: | + Sets the enabled AWS services to be expanded when AWS payload tagging is enabled. + version_added: + v2.17.0: - DD_TRACE__ENABLED: + DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING: + type: String + default: None + description: | + Enables AWS request payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. + version_added: + v2.17.0: + + DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING: + type: String + default: None + description: | + Enables AWS response payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. + version_added: + v2.17.0: + + DD_TRACE_ENABLED: type: Boolean default: True + description: | - Enables to be patched. For example, ``DD_TRACE_DJANGO_ENABLED=false`` will disable the Django - integration from being installed. + Enable sending of spans to the Agent. Note that instrumentation will still be installed and spans will be + generated. + version_added: - v0.41.0: + v0.41.0: | + Formerly named ``DATADOG_TRACE_ENABLED`` - DD_PATCH_MODULES: + DD_TRACE_HEADER_TAGS: description: | - Override the modules patched for this execution of the program. Must be - a list in the ``module1:boolean,module2:boolean`` format. For example, - ``boto:true,redis:false``. - version_added: - v0.55.0: | - Formerly named ``DATADOG_PATCH_MODULES`` + A map of case-insensitive http headers to tag names. Automatically applies matching header values as tags on request and response spans. For example if + ``DD_TRACE_HEADER_TAGS=User-Agent:http.useragent,content-type:http.content_type``. The value of the header will be stored in tags with the name ``http.useragent`` and ``http.content_type``. - DD_LOGS_INJECTION: + If a tag name is not supplied the header name will be used. For example if + ``DD_TRACE_HEADER_TAGS=User-Agent,content-type``. The value of http header will be stored in tags with the names ``http..headers.user-agent`` and ``http..headers.content-type``. + + DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING: type: Boolean - default: False - description: Enables :ref:`Logs Injection`. + default: True + description: Send query strings in http.url tag in http client integrations. - DD_AGENT_HOST: + DD_TRACE_HTTP_SERVER_ERROR_STATUSES: type: String - default: | - ``localhost`` + default: "500-599" + description: | - The host name to use to connect the Datadog agent for traces. The host name - can be IPv4, IPv6, or a domain name. If ``DD_TRACE_AGENT_URL`` is specified, the - value of ``DD_AGENT_HOST`` is ignored. - - Example for IPv4: ``DD_AGENT_HOST=192.168.10.1`` - - Example for IPv6: ``DD_AGENT_HOST=2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF`` + Comma-separated list of HTTP status codes that should be considered errors when returned by an HTTP request. + Multiple comma separated error ranges can be set (ex: ``200,400-404,500-599``). + The status codes are used to set the ``error`` field on the span. - Example for domain name: ``DD_AGENT_HOST=host`` + DD_TRACE_METHODS: + type: String + default: "" + + description: | + Specify methods to trace. For example: ``mod.submod:method1,method2;mod.submod:Class.method1``. + Note that this setting is only compatible with ``ddtrace-run``, and that it doesn't work for methods implemented + by libraries for which there's an integration in ``ddtrace/contrib``. + version_added: - v0.17.0: - v1.7.0: + v2.1.0: - DD_TRACE_AGENT_URL: - type: URL + DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: default: | - ``unix:///var/run/datadog/apm.socket`` if available - otherwise ``http://localhost:8126`` - description: | - The URL to use to connect the Datadog agent for traces. The url can start with - ``http://`` to connect using HTTP or with ``unix://`` to use a Unix - Domain Socket. - - Example for http url: ``DD_TRACE_AGENT_URL=http://localhost:8126`` - - Example for UDS: ``DD_TRACE_AGENT_URL=unix:///var/run/datadog/apm.socket`` + ``'(?ix)(?:(?:"|%22)?)(?:(?:old[-_]?|new[-_]?)?p(?:ass)?w(?:or)?d(?:1|2)?|pass(?:[-_]?phrase)?|secret|(?:api[-_]?|private[-_]?|public[-_]?|access[-_]?|secret[-_]?)key(?:[-_]?id)?|token|consumer[-_]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|(?: bearer(?:\\s|%20)+[a-z0-9._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+/=-]|%3D|%2F|%2B)+)?|-{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY-{5}[^\\-]+-{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY(?:-{5})?(?:\\n|%0A)?|(?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)(?:\\s|%20|%09)+(?:[a-z0-9/.+]|%2F|%5C|%2B){100,}(?:=|%3D)*(?:(?:\\s|%20|%09)+[a-z0-9._-]+)?)'`` + + description: A regexp to redact sensitive query strings. Obfuscation disabled if set to empty string + + version_added: + v1.19.0: | + ``DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP`` replaces ``DD_TRACE_OBFUSCATION_QUERY_STRING_PATTERN`` which is deprecated + and will be deleted in 2.0.0 - DD_DOGSTATSD_URL: - type: URL - default: | - ``unix:///var/run/datadog/dsd.socket`` if available - otherwise ``udp://localhost:8125`` + DD_TRACE_OTEL_ENABLED: + type: Boolean + default: False + description: | - The URL to use to connect the Datadog agent for Dogstatsd metrics. The url can start with - ``udp://`` to connect using UDP or with ``unix://`` to use a Unix - Domain Socket. + When used with ``ddtrace-run`` this configuration enables OpenTelemetry support. To enable OpenTelemetry without `ddtrace-run` refer + to the following :mod:`docs `. + + version_added: + v1.12.0: - Example for UDP url: ``DD_DOGSTATSD_URL=udp://localhost:8125`` + DD_TRACE_PARTIAL_FLUSH_ENABLED: + type: Boolean + default: True + description: Prevents large payloads being sent to APM. - Example for UDS: ``DD_DOGSTATSD_URL=unix:///var/run/datadog/dsd.socket`` + DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: + type: Integer + default: 300 + description: Maximum number of spans sent per trace per payload when ``DD_TRACE_PARTIAL_FLUSH_ENABLED=True``. - DD_TRACE_AGENT_TIMEOUT_SECONDS: - type: Float - default: 2.0 - description: The timeout in float to use to connect to the Datadog agent. - - DD_TRACE_WRITER_BUFFER_SIZE_BYTES: - type: Int - default: 8388608 - description: The max size in bytes of traces to buffer between flushes to the agent. - - DD_TRACE_WRITER_MAX_PAYLOAD_SIZE_BYTES: - type: Int - default: 8388608 - description: | - The max size in bytes of each payload item sent to the trace agent. If the max payload size is greater than buffer size, - then max size of each payload item will be the buffer size. - - DD_TRACE_WRITER_INTERVAL_SECONDS: - type: Float - default: 1.0 - description: The time between each flush of traces to the trace agent. - - DD_TRACE_STARTUP_LOGS: + DD_TRACE_PROPAGATION_EXTRACT_FIRST: type: Boolean default: False - description: Enable or disable start up diagnostic logging. - - DD_TRACE_RATE_LIMIT: - type: int - default: 100 - description: | - Maximum number of traces per second to sample. Set a rate limit to avoid the ingestion volume overages in the case of traffic spikes. This configuration - is only applied when client based sampling is configured, otherwise agent based rate limits are used. - version_added: - v0.33.0: - v2.15.0: Only applied when DD_TRACE_SAMPLE_RATE, DD_TRACE_SAMPLING_RULES, or DD_SPAN_SAMPLING_RULE are set. - - DD_TRACE_SAMPLING_RULES: - type: JSON array - description: | - A JSON array of objects. Each object must have a “sample_rate”, and the “name”, “service”, "resource", and "tags" fields are optional. The “sample_rate” value must be between 0.0 and 1.0 (inclusive). - - **Example:** ``DD_TRACE_SAMPLING_RULES='[{"sample_rate":0.5,"service":"my-service","resource":"my-url","tags":{"my-tag":"example"}}]'`` - - **Note** that the JSON object must be included in single quotes (') to avoid problems with escaping of the double quote (") character.' - version_added: - v1.19.0: added support for "resource" - v1.20.0: added support for "tags" - v2.8.0: added lazy sampling support, so that spans are evaluated at the end of the trace, guaranteeing more metadata to evaluate against. - - DD_SPAN_SAMPLING_RULES: - type: string - description: | - A JSON array of objects. Each object must have a "name" and/or "service" field, while the "max_per_second" and "sample_rate" fields are optional. - The "sample_rate" value must be between 0.0 and 1.0 (inclusive), and will default to 1.0 (100% sampled). - The "max_per_second" value must be >= 0 and will default to no limit. - The "service" and "name" fields can be glob patterns: - "*" matches any substring, including the empty string, - "?" matches exactly one of any character, and any other character matches exactly one of itself. - - **Example:** ``DD_SPAN_SAMPLING_RULES='[{"sample_rate":0.5,"service":"my-serv*","name":"flask.re?uest"}]'`` - - version_added: - v1.4.0: - - DD_SPAN_SAMPLING_RULES_FILE: - type: string - description: | - A path to a JSON file containing span sampling rules organized as JSON array of objects. - For the rules each object must have a "name" and/or "service" field, and the "sample_rate" field is optional. - The "sample_rate" value must be between 0.0 and 1.0 (inclusive), and will default to 1.0 (100% sampled). - The "max_per_second" value must be >= 0 and will default to no limit. - The "service" and "name" fields are glob patterns, where "glob" means: - "*" matches any substring, including the empty string, - "?" matches exactly one of any character, and any other character matches exactly one of itself. - - **Example:** ``DD_SPAN_SAMPLING_RULES_FILE="data/span_sampling_rules.json"'`` - **Example File Contents:** ``[{"sample_rate":0.5,"service":"*-service","name":"my-name-????", "max_per_second":"20"}, {"service":"xy?","name":"a*c"}]`` - + description: Whether the propagator stops after extracting the first header. + version_added: - v1.4.0: - - DD_TRACE_HEADER_TAGS: - description: | - A map of case-insensitive http headers to tag names. Automatically applies matching header values as tags on request and response spans. For example if - ``DD_TRACE_HEADER_TAGS=User-Agent:http.useragent,content-type:http.content_type``. The value of the header will be stored in tags with the name ``http.useragent`` and ``http.content_type``. - - If a tag name is not supplied the header name will be used. For example if - ``DD_TRACE_HEADER_TAGS=User-Agent,content-type``. The value of http header will be stored in tags with the names ``http..headers.user-agent`` and ``http..headers.content-type``. + v2.3.0: - DD_TRACE_API_VERSION: - default: | - ``v0.5`` + DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED: + type: Boolean + default: False + description: | - The trace API version to use when sending traces to the Datadog agent. - - Currently, the supported versions are: ``v0.4`` and ``v0.5``. - version_added: - v0.56.0: - v1.7.0: default changed to ``v0.5``. - v1.19.1: default reverted to ``v0.4``. - v2.4.0: default changed to ``v0.5``. - - DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: - default: | - ``'(?ix)(?:(?:"|%22)?)(?:(?:old[-_]?|new[-_]?)?p(?:ass)?w(?:or)?d(?:1|2)?|pass(?:[-_]?phrase)?|secret|(?:api[-_]?|private[-_]?|public[-_]?|access[-_]?|secret[-_]?)key(?:[-_]?id)?|token|consumer[-_]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|(?: bearer(?:\\s|%20)+[a-z0-9._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+/=-]|%3D|%2F|%2B)+)?|-{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY-{5}[^\\-]+-{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY(?:-{5})?(?:\\n|%0A)?|(?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)(?:\\s|%20|%09)+(?:[a-z0-9/.+]|%2F|%5C|%2B){100,}(?:=|%3D)*(?:(?:\\s|%20|%09)+[a-z0-9._-]+)?)'`` - description: A regexp to redact sensitive query strings. Obfuscation disabled if set to empty string + Enables propagation of baggage items through http headers with prefix ``ot-baggage-``. + version_added: - v1.19.0: | - ``DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP`` replaces ``DD_TRACE_OBFUSCATION_QUERY_STRING_PATTERN`` which is deprecated - and will be deleted in 2.0.0 + v2.4.0: DD_TRACE_PROPAGATION_STYLE: default: | ``datadog,tracecontext`` + description: | Comma separated list of propagation styles used for extracting trace context from inbound request headers and injecting trace context into outbound request headers. @@ -338,9 +300,64 @@ The following environment variables for the tracer are supported: v1.7.0: Added support for ``tracecontext`` W3C headers. Changed the default value to ``DD_TRACE_PROPAGATION_STYLE="tracecontext,datadog"``. v2.6.0: Updated default value to ``datadog,tracecontext``. + DD_TRACE_SPAN_TRACEBACK_MAX_SIZE: + type: Integer + default: 30 + + description: | + The maximum length of a traceback included in a span. + + version_added: + v2.3.0: + + DD_TRACE_WRITER_BUFFER_SIZE_BYTES: + type: Int + default: 8388608 + description: The max size in bytes of traces to buffer between flushes to the agent. + + DD_TRACE_WRITER_INTERVAL_SECONDS: + type: Float + default: 1.0 + description: The time between each flush of traces to the trace agent. + + DD_TRACE_WRITER_MAX_PAYLOAD_SIZE_BYTES: + type: Int + default: 8388608 + + description: | + The max size in bytes of each payload item sent to the trace agent. If the max payload size is greater than buffer size, + then max size of each payload item will be the buffer size. + + DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH: + type: Integer + default: 512 + + description: | + The maximum length of ``x-datadog-tags`` header allowed in the Datadog propagation style. + Must be a value between 0 to 512. If 0, propagation of ``x-datadog-tags`` is disabled. + + DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE: + type: String + default: "auto" + + description: | + Controls whether module cloning logic is executed by ``ddtrace-run``. Module cloning involves saving copies of dependency modules for internal use by ``ddtrace`` + that will be unaffected by future imports of and changes to those modules by application code. Valid values for this variable are ``1``, ``0``, and ``auto``. ``1`` tells + ``ddtrace`` to run its module cloning logic unconditionally, ``0`` tells it not to run that logic, and ``auto`` tells it to run module cloning logic only if ``gevent`` + is accessible from the application's runtime. + + version_added: + v1.9.0: + +Trace Context propagation +------------------------- + +.. ddtrace-configuration-options:: + DD_TRACE_PROPAGATION_STYLE_EXTRACT: default: | ``datadog,tracecontext`` + description: | Comma separated list of propagation styles used for extracting trace context from inbound request headers. @@ -360,6 +377,7 @@ The following environment variables for the tracer are supported: DD_TRACE_PROPAGATION_STYLE_INJECT: default: | ``tracecontext,datadog`` + description: | Comma separated list of propagation styles used for injecting trace context into outbound request headers. @@ -376,101 +394,49 @@ The following environment variables for the tracer are supported: version_added: v1.7.0: The ``b3multi`` propagation style was added and ``b3`` was deprecated in favor it. - DD_TRACE_PROPAGATION_EXTRACT_FIRST: - type: Boolean - default: False - description: Whether the propagator stops after extracting the first header. - version_added: - v2.3.0: - - DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH: - type: Integer - default: 512 - description: | - The maximum length of ``x-datadog-tags`` header allowed in the Datadog propagation style. - Must be a value between 0 to 512. If 0, propagation of ``x-datadog-tags`` is disabled. - - DD_TRACE_PARTIAL_FLUSH_ENABLED: - type: Boolean - default: True - description: Prevents large payloads being sent to APM. - - DD_ASGI_TRACE_WEBSOCKET: - default: False - description: | - Enables tracing ASGI websockets. Please note that the websocket span duration will last until the - connection is closed, which can result in long running spans. +AppSec +------ - version_added: - v2.7.0: +.. ddtrace-configuration-options:: - DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: - type: Integer - default: 300 - description: Maximum number of spans sent per trace per payload when ``DD_TRACE_PARTIAL_FLUSH_ENABLED=True``. + DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: + type: String + default: "safe" + + description: | + Sets the mode for the automated user login events tracking feature which sets some traces on each user login event. The + supported modes are ``safe`` which will only store the user id or primary key, ``extended`` which will also store + the username, email and full name and ``disabled``. Note that this feature requires ``DD_APPSEC_ENABLED`` to be + set to ``true`` to work. + + version_added: + v1.17.0: Added support to the Django integration. No other integrations support this configuration. DD_APPSEC_ENABLED: type: Boolean default: False description: Whether to enable AppSec monitoring. - DD_APPSEC_SCA_ENABLED: - type: Boolean - default: None - description: Whether to enable/disable SCA (Software Composition Analysis). - - DD_APPSEC_RULES: - type: String - description: Path to a json file containing AppSec rules. - - DD_COMPILE_DEBUG: - type: Boolean - default: False - description: Compile Cython extensions in RelWithDebInfo mode (with debug info, but no debug code or asserts) - DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP: default: | ``(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization`` + description: Sensitive parameter key regexp for obfuscation. DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP: default: | ``(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}`` + description: Sensitive parameter value regexp for obfuscation. - DD_SUBPROCESS_SENSITIVE_WILDCARDS: + DD_APPSEC_RULES: type: String - description: | - Add more possible matches to the internal list of subprocess execution argument scrubbing. Must be a comma-separated list and - each item can take `fnmatch` style wildcards, for example: ``*ssn*,*personalid*,*idcard*,*creditcard*``. - - DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING: - type: Boolean - default: True - description: Send query strings in http.url tag in http client integrations. + description: Path to a json file containing AppSec rules. - DD_HTTP_SERVER_TAG_QUERY_STRING: + DD_APPSEC_SCA_ENABLED: type: Boolean - default: True - description: Send query strings in http.url tag in http server integrations. - - DD_TRACE_HTTP_SERVER_ERROR_STATUSES: - type: String - default: "500-599" - description: | - Comma-separated list of HTTP status codes that should be considered errors when returned by an HTTP request. - Multiple comma separated error ranges can be set (ex: ``200,400-404,500-599``). - The status codes are used to set the ``error`` field on the span. - - DD_TRACE_METHODS: - type: String - default: "" - description: | - Specify methods to trace. For example: ``mod.submod:method1,method2;mod.submod:Class.method1``. - Note that this setting is only compatible with ``ddtrace-run``, and that it doesn't work for methods implemented - by libraries for which there's an integration in ``ddtrace/contrib``. - version_added: - v2.1.0: + default: None + description: Whether to enable/disable SCA (Software Composition Analysis). DD_IAST_ENABLED: type: Boolean @@ -482,223 +448,398 @@ The following environment variables for the tracer are supported: default: 2 description: Number of requests analyzed at the same time. - DD_IAST_VULNERABILITIES_PER_REQUEST: - type: Integer - default: 2 - description: Number of vulnerabilities reported in each request. - - DD_IAST_WEAK_HASH_ALGORITHMS: - type: String - default: "MD5,SHA1" - description: Weak hashing algorithms that should be reported, comma separated. - - DD_IAST_WEAK_CIPHER_ALGORITHMS: - type: String - default: "DES,Blowfish,RC2,RC4,IDEA" - description: Weak cipher algorithms that should be reported, comma separated. - DD_IAST_REDACTION_ENABLED: type: Boolean default: True + description: | Replace potentially sensitive information in the vulnerability report, like passwords with ``*`` for non tainted strings and ``abcde...`` for tainted ones. This will use the regular expressions of the two next settings to decide what to scrub. + version_added: v1.17.0: DD_IAST_REDACTION_NAME_PATTERN: type: String + default: | ``(?i)^.*(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)`` + description: | Regular expression containing key or name style strings matched against vulnerability origin and evidence texts. If it matches, the scrubbing of the report will be enabled. + version_added: v1.17.0: DD_IAST_REDACTION_VALUE_PATTERN: type: String + default: | ``(?i)bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}`` + description: | Regular expression containing value style strings matched against vulnerability origin and evidence texts. If it matches, the scrubbing of the report will be enabled. + version_added: v1.17.0: - DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE: + DD_IAST_VULNERABILITIES_PER_REQUEST: + type: Integer + default: 2 + description: Number of vulnerabilities reported in each request. + + DD_IAST_WEAK_HASH_ALGORITHMS: type: String - default: "auto" - description: | - Controls whether module cloning logic is executed by ``ddtrace-run``. Module cloning involves saving copies of dependency modules for internal use by ``ddtrace`` - that will be unaffected by future imports of and changes to those modules by application code. Valid values for this variable are ``1``, ``0``, and ``auto``. ``1`` tells - ``ddtrace`` to run its module cloning logic unconditionally, ``0`` tells it not to run that logic, and ``auto`` tells it to run module cloning logic only if ``gevent`` - is accessible from the application's runtime. - version_added: - v1.9.0: + default: "MD5,SHA1" + description: Weak hashing algorithms that should be reported, comma separated. - DD_TEST_SESSION_NAME: + DD_IAST_WEAK_CIPHER_ALGORITHMS: type: String - default: (autodetected) - description: | - Configures the ``CIVisibility`` service to use the given string as the value of the ``test_session.name`` tag in - test events. If not specified, this string will be constructed from the CI job id (if available) and the test - command used to start the test session. - version_added: - v2.16.0: + default: "DES,Blowfish,RC2,RC4,IDEA" + description: Weak cipher algorithms that should be reported, comma separated. + + +Test Visibility +--------------- + +.. ddtrace-configuration-options:: DD_CIVISIBILITY_AGENTLESS_ENABLED: type: Boolean default: False + description: | Configures the ``CIVisibility`` service to use a test-reporting ``CIVisibilityWriter``. This writer sends payloads for traces on which it's used to the intake endpoint for Datadog CI Visibility. If there is a reachable Datadog agent that supports proxying these requests, the writer will send its payloads to that agent instead. + version_added: v1.12.0: DD_CIVISIBILITY_AGENTLESS_URL: type: String default: "" + description: | Configures the ``CIVisibility`` service to send event payloads to the specified host. If unspecified, the host "https://citestcycle-intake." is used, where ```` is replaced by that environment variable's value, or "datadoghq.com" if unspecified. + version_added: v1.13.0: DD_CIVISIBILITY_ITR_ENABLED: type: Boolean default: True + description: | Configures the ``CIVisibility`` service to query the Datadog API to decide whether to enable the Datadog `Intelligent Test Runner _`. Setting the variable to ``false`` will skip querying the API and disable code coverage collection and test skipping. + version_added: v1.13.0: DD_CIVISIBILITY_LOG_LEVEL: type: String default: "info" + description: | Configures the ``CIVisibility`` service to replace the default Datadog logger's stream handler with one that only displays messages related to the ``CIVisibility`` service, at a level of or higher than the given log level. The Datadog logger's file handler is unaffected. Valid, case-insensitive, values are ``critical``, ``error``, ``warning``, ``info``, or ``debug``. A value of ``none`` silently disables the logger. Note: enabling debug logging with the ``DD_TRACE_DEBUG`` environment variable overrides this behavior. + version_added: v2.5.0: - DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: + DD_TEST_SESSION_NAME: + type: String + default: (autodetected) + + description: | + Configures the ``CIVisibility`` service to use the given string as the value of the ``test_session.name`` tag in + test events. If not specified, this string will be constructed from the CI job id (if available) and the test + command used to start the test session. + + version_added: + v2.16.0: + + +Agent +----- + +.. ddtrace-configuration-options:: + + DD_AGENT_HOST: + type: String + + default: | + ``localhost`` + + description: | + The host name to use to connect the Datadog agent for traces. The host name + can be IPv4, IPv6, or a domain name. If ``DD_TRACE_AGENT_URL`` is specified, the + value of ``DD_AGENT_HOST`` is ignored. + + Example for IPv4: ``DD_AGENT_HOST=192.168.10.1`` + + Example for IPv6: ``DD_AGENT_HOST=2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF`` + + Example for domain name: ``DD_AGENT_HOST=host`` + + version_added: + v0.17.0: + v1.7.0: + + DD_DOGSTATSD_URL: + type: URL + + default: | + ``unix:///var/run/datadog/dsd.socket`` if available + otherwise ``udp://localhost:8125`` + + description: | + The URL to use to connect the Datadog agent for Dogstatsd metrics. The url can start with + ``udp://`` to connect using UDP or with ``unix://`` to use a Unix + Domain Socket. + + Example for UDP url: ``DD_DOGSTATSD_URL=udp://localhost:8125`` + + Example for UDS: ``DD_DOGSTATSD_URL=unix:///var/run/datadog/dsd.socket`` + + DD_PATCH_MODULES: + description: | + Override the modules patched for this execution of the program. Must be + a list in the ``module1:boolean,module2:boolean`` format. For example, + ``boto:true,redis:false``. + + version_added: + v0.55.0: | + Formerly named ``DATADOG_PATCH_MODULES`` + + DD_SITE: + default: datadoghq.com + + description: | + Specify which site to use for uploading profiles and logs. Set to + ``datadoghq.eu`` to use EU site. + + DD_TAGS: + description: | + Set global tags to be attached to every span. Value must be either comma or space separated. e.g. ``key1:value1,key2:value2`` or ``key1:value key2:value2``. + + If a tag value is not supplied the value will be an empty string. e.g. ``key1,key2`` or ``key1 key2``. + + version_added: + v0.38.0: Comma separated support added + v0.48.0: Space separated support added + + DD_TRACE_AGENT_TIMEOUT_SECONDS: + type: Float + default: 2.0 + description: The timeout in float to use to connect to the Datadog agent. + + DD_TRACE_AGENT_URL: + type: URL + + default: | + ``unix:///var/run/datadog/apm.socket`` if available + otherwise ``http://localhost:8126`` + + description: | + The URL to use to connect the Datadog agent for traces. The url can start with + ``http://`` to connect using HTTP or with ``unix://`` to use a Unix + Domain Socket. + + Example for http url: ``DD_TRACE_AGENT_URL=http://localhost:8126`` + + Example for UDS: ``DD_TRACE_AGENT_URL=unix:///var/run/datadog/apm.socket`` + +Logs +---- + +.. ddtrace-configuration-options:: + + DD_LOGS_INJECTION: + type: Boolean + default: False + description: Enables :ref:`Logs Injection`. + + DD_TRACE_DEBUG: + type: Boolean + default: False + + description: | + Enables debug logging in the tracer. + + Can be used with `DD_TRACE_LOG_FILE` to route logs to a file. + + version_added: + v0.41.0: | + Formerly named ``DATADOG_TRACE_DEBUG`` + + DD_TRACE_LOG_FILE: + description: | + Directs `ddtrace` logs to a specific file. Note: The default backup count is 1. For larger logs, use with ``DD_TRACE_LOG_FILE_SIZE_BYTES``. + To fine tune the logging level, use with ``DD_TRACE_LOG_FILE_LEVEL``. + + DD_TRACE_LOG_FILE_LEVEL: + default: DEBUG + + description: | + Configures the ``RotatingFileHandler`` used by the `ddtrace` logger to write logs to a file based on the level specified. + Defaults to `DEBUG`, but will accept the values found in the standard **logging** library, such as WARNING, ERROR, and INFO, + if further customization is needed. Files are not written to unless ``DD_TRACE_LOG_FILE`` has been defined. + + DD_TRACE_LOG_FILE_SIZE_BYTES: + type: Int + default: 15728640 + + description: | + Max size for a file when used with `DD_TRACE_LOG_FILE`. When a log has exceeded this size, there will be one backup log file created. + In total, the files will store ``2 * DD_TRACE_LOG_FILE_SIZE_BYTES`` worth of logs. + + DD_TRACE_STARTUP_LOGS: + type: Boolean + default: False + description: Enable or disable start up diagnostic logging. + +Sampling +-------- + +.. ddtrace-configuration-options:: + + DD_SPAN_SAMPLING_RULES: + type: string + + description: | + A JSON array of objects. Each object must have a "name" and/or "service" field, while the "max_per_second" and "sample_rate" fields are optional. + The "sample_rate" value must be between 0.0 and 1.0 (inclusive), and will default to 1.0 (100% sampled). + The "max_per_second" value must be >= 0 and will default to no limit. + The "service" and "name" fields can be glob patterns: + "*" matches any substring, including the empty string, + "?" matches exactly one of any character, and any other character matches exactly one of itself. + + **Example:** ``DD_SPAN_SAMPLING_RULES='[{"sample_rate":0.5,"service":"my-serv*","name":"flask.re?uest"}]'`` + + version_added: + v1.4.0: + + DD_SPAN_SAMPLING_RULES_FILE: + type: string + + description: | + A path to a JSON file containing span sampling rules organized as JSON array of objects. + For the rules each object must have a "name" and/or "service" field, and the "sample_rate" field is optional. + The "sample_rate" value must be between 0.0 and 1.0 (inclusive), and will default to 1.0 (100% sampled). + The "max_per_second" value must be >= 0 and will default to no limit. + The "service" and "name" fields are glob patterns, where "glob" means: + "*" matches any substring, including the empty string, + "?" matches exactly one of any character, and any other character matches exactly one of itself. + + **Example:** ``DD_SPAN_SAMPLING_RULES_FILE="data/span_sampling_rules.json"'`` + **Example File Contents:** ``[{"sample_rate":0.5,"service":"*-service","name":"my-name-????", "max_per_second":"20"}, {"service":"xy?","name":"a*c"}]`` + + version_added: + v1.4.0: + + DD_TRACE_RATE_LIMIT: + type: int + default: 100 + + description: | + Maximum number of traces per second to sample. Set a rate limit to avoid the ingestion volume overages in the case of traffic spikes. This configuration + is only applied when client based sampling is configured, otherwise agent based rate limits are used. + + version_added: + v0.33.0: + v2.15.0: Only applied when DD_TRACE_SAMPLE_RATE, DD_TRACE_SAMPLING_RULES, or DD_SPAN_SAMPLING_RULE are set. + + DD_TRACE_SAMPLING_RULES: + type: JSON array + + description: | + A JSON array of objects. Each object must have a “sample_rate”, and the “name”, “service”, "resource", and "tags" fields are optional. The “sample_rate” value must be between 0.0 and 1.0 (inclusive). + + **Example:** ``DD_TRACE_SAMPLING_RULES='[{"sample_rate":0.5,"service":"my-service","resource":"my-url","tags":{"my-tag":"example"}}]'`` + + **Note** that the JSON object must be included in single quotes (') to avoid problems with escaping of the double quote (") character.' + + version_added: + v1.19.0: added support for "resource" + v1.20.0: added support for "tags" + v2.8.0: added lazy sampling support, so that spans are evaluated at the end of the trace, guaranteeing more metadata to evaluate against. + +Other +----- + +.. ddtrace-configuration-options:: + + DD_COMPILE_DEBUG: + type: Boolean + default: False + description: Compile Cython extensions in RelWithDebInfo mode (with debug info, but no debug code or asserts) + + DD_INSTRUMENTATION_TELEMETRY_ENABLED: + type: Boolean + default: True + + description: | + Enables sending :ref:`telemetry ` events to the agent. + + DD_RUNTIME_METRICS_ENABLED: + type: Boolean + default: False + + description: | + When used with ``ddtrace-run`` this configuration enables sending runtime metrics to Datadog. + These metrics track the memory management and concurrency of the python runtime. + Refer to the following `docs ` _ for more information. + + DD_SUBPROCESS_SENSITIVE_WILDCARDS: + type: String + + description: | + Add more possible matches to the internal list of subprocess execution argument scrubbing. Must be a comma-separated list and + each item can take `fnmatch` style wildcards, for example: ``*ssn*,*personalid*,*idcard*,*creditcard*``. + + DD_USER_MODEL_EMAIL_FIELD: type: String - default: "safe" + default: "" + description: | - Sets the mode for the automated user login events tracking feature which sets some traces on each user login event. The - supported modes are ``safe`` which will only store the user id or primary key, ``extended`` which will also store - the username, email and full name and ``disabled``. Note that this feature requires ``DD_APPSEC_ENABLED`` to be - set to ``true`` to work. + Field to be used to read the user email when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. + version_added: - v1.17.0: Added support to the Django integration. No other integrations support this configuration. + v1.15.0: DD_USER_MODEL_LOGIN_FIELD: type: String default: "" + description: | Field to be used to read the user login when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. Please note that, if set, this field will be used to retrieve the user login even if ``DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING`` is set to ``safe`` and, in some cases, the selected field could hold potentially private information. - version_added: - v1.15.0: - - DD_USER_MODEL_EMAIL_FIELD: - type: String - default: "" - description: | - Field to be used to read the user email when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. + version_added: v1.15.0: DD_USER_MODEL_NAME_FIELD: type: String default: "" + description: | Field to be used to read the user name when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. + version_added: v1.15.0: - DD_TRACE_SPAN_TRACEBACK_MAX_SIZE: - type: Integer - default: 30 - description: | - The maximum length of a traceback included in a span. - version_added: - v2.3.0: - - DD_BOTOCORE_SERVICE: - type: String - default: "aws" - description: | - Set the service name, allowing default service name overrides for traces in botocore. - version_added: - v2.11.0: - - DD_BOTOCORE_PROPAGATION_ENABLED: - type: Boolean - default: False - description: | - Enables trace context propagation connecting producer and consumer spans within a single trace for AWS SQS, SNS, and Kinesis messaging services. - version_added: - v2.6.0: - - DD_BOTOCORE_EMPTY_POLL_ENABLED: - type: Boolean - default: True - description: | - Enables creation of consumer span when AWS SQS and AWS Kinesis ``poll()`` operations return no records. When disabled, no consumer span is created - if no records are returned. - version_added: - v2.6.0: - - DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING: - type: String - default: None - description: | - Enables AWS request payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. - version_added: - v2.17.0: - - DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING: - type: String - default: None - description: | - Enables AWS response payload tagging when set to ``"all"`` or a valid comma-separated list of ``JSONPath``\s. - version_added: - v2.17.0: - - DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH: - type: Integer - default: 10 - description: | - Sets the depth of expanding the JSON AWS payload after which we stop creating tags. - version_added: - v2.17.0: - - DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_TAGS: - type: Integer - default: 758 - description: | - Sets the the maximum number of tags that will be added when expanding an AWS payload. - version_added: - v2.17.0: - - DD_TRACE_CLOUD_PAYLOAD_TAGGING_SERVICES: - type: Set - default: {"s3", "sns", "sqs", "kinesis", "eventbridge"} - description: | - Sets the enabled AWS services to be expanded when AWS payload tagging is enabled. - version_added: - v2.17.0: .. _Unified Service Tagging: https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/ From c9c3891d69d324d14c00320d74e0a649c0a360fd Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 13 Nov 2024 21:08:49 +0000 Subject: [PATCH 150/372] ci: reduce API requests in download wheel job (#11373) We reduce the number of requests to the GitHub API made by the GitLab wheel download workflow to reduce the chances of going over the request rate limit. We also make sure that the job re-runs reactively when the GitHub workflow has completed. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/download-wheels-from-gh-actions.sh | 49 +++++++++++++--------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/.gitlab/download-wheels-from-gh-actions.sh b/.gitlab/download-wheels-from-gh-actions.sh index 2803558d784..2ffe403cd14 100755 --- a/.gitlab/download-wheels-from-gh-actions.sh +++ b/.gitlab/download-wheels-from-gh-actions.sh @@ -6,20 +6,26 @@ if [ -z "$CI_COMMIT_SHA" ]; then exit 1 fi -echo "Querying for RUN_ID" - -timeout=600 # 10 minutes -start_time=$(date +%s) -end_time=$((start_time + timeout)) -# Loop for 10 minutes waiting for run to appear in github -while [ $(date +%s) -lt $end_time ]; do - RUN_ID=$(gh run ls --repo DataDog/dd-trace-py --commit=$CI_COMMIT_SHA --workflow=build_deploy.yml --json databaseId --jq "first (.[]) | .databaseId") - if [ -n "$RUN_ID" ]; then - break; - fi - echo "Waiting for RUN_ID" - sleep 20 -done +RUN_ID=$(gh run ls --repo DataDog/dd-trace-py --commit=$CI_COMMIT_SHA --workflow=build_deploy.yml --json databaseId --jq "first (.[]) | .databaseId") +if [ -n "$RUN_ID" ]; then + # The job has not started yet. Give it time to start + sleep 180 # 3 minutes + + echo "Querying for RUN_ID" + + timeout=600 # 10 minutes + start_time=$(date +%s) + end_time=$((start_time + timeout)) + # Loop for 10 minutes waiting for run to appear in github + while [ $(date +%s) -lt $end_time ]; do + RUN_ID=$(gh run ls --repo DataDog/dd-trace-py --commit=$CI_COMMIT_SHA --workflow=build_deploy.yml --json databaseId --jq "first (.[]) | .databaseId") + if [ -n "$RUN_ID" ]; then + break; + fi + echo "Waiting for RUN_ID" + sleep 60 + done +fi if [ -z "$RUN_ID" ]; then echo "RUN_ID not found" @@ -27,16 +33,21 @@ if [ -z "$RUN_ID" ]; then fi echo "Found RUN_ID: $RUN_ID" -echo "Waiting for workflow to finish" - -# wait for run to finish -gh run watch $RUN_ID --interval 45 --exit-status 1 --repo DataDog/dd-trace-py mkdir pywheels cd pywheels -echo "Github workflow finished. Downloading wheels" +if [[ $(gh run view $RUN_ID --exit-status --json status --jq .status) != "completed" ]]; then + echo "Waiting for workflow to finish" + + # Give time to the job to finish + sleep 300 # 5 minutes + # wait for run to finish + gh run watch $RUN_ID --interval 60 --exit-status 1 --repo DataDog/dd-trace-py +fi + +echo "Github workflow finished. Downloading wheels" # download all wheels gh run download $RUN_ID --repo DataDog/dd-trace-py From f7c102ee1dc2c327ed2bf72732d2d836962b11f8 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 13 Nov 2024 22:04:56 +0000 Subject: [PATCH 151/372] ci: add suitespec to GitLab (#10877) We port the suitespec machinery to GitLab. The current implementation uses generated child pipelines, with the tests YAML configuration automatically generated to include the required jobs based on the files listed in the PR. The job information previously contained in the config.templ.yaml file has been migrated to the jobspec.yml configuration. The jobspec can be "modular", in the sense that it can be split into multiple jobspec.yml file within the `tests/` subtree (e.g. `tests/jobspec.yml`, `tests/debugging/jobspec.yml`, ...) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim --- .circleci/config.templ.yml | 2 +- .github/workflows/requirements-locks.yml | 2 +- .gitignore | 2 + .gitlab-ci.yml | 35 +- .gitlab/package.yml | 21 -- .gitlab/testrunner.yml | 8 + .gitlab/tests.yml | 66 +++- .gitlab/tests/appsec.yml | 70 ---- .gitlab/tests/ci_visibility.yml | 28 -- .gitlab/tests/contrib.yml | 394 ----------------------- .gitlab/tests/core.yml | 49 --- .gitlab/tests/debugging.yml | 5 - .gitlab/tests/llmobs.yml | 28 -- .gitlab/tests/profiling.yml | 7 - .gitlab/tests/tracer.yml | 7 - .riot/requirements/196755b.txt | 20 ++ docker-compose.yml | 2 +- riotfile.py | 8 + scripts/gen_circleci_config.py | 22 +- scripts/gen_gitlab_config.py | 153 +++++++++ scripts/needs_testrun.py | 97 ++++-- tests/.suitespec.json | 54 +--- tests/README.md | 38 +++ tests/appsec/jobspec.yml | 65 ++++ tests/ci_visibility/jobspec.yml | 29 ++ tests/contrib/jobspec.yml | 360 +++++++++++++++++++++ tests/debugging/jobspec.yml | 5 + tests/jobspec.yml | 46 +++ tests/llmobs/jobspec.yml | 27 ++ 29 files changed, 929 insertions(+), 721 deletions(-) create mode 100644 .gitlab/testrunner.yml delete mode 100644 .gitlab/tests/appsec.yml delete mode 100644 .gitlab/tests/ci_visibility.yml delete mode 100644 .gitlab/tests/contrib.yml delete mode 100644 .gitlab/tests/core.yml delete mode 100644 .gitlab/tests/debugging.yml delete mode 100644 .gitlab/tests/llmobs.yml delete mode 100644 .gitlab/tests/profiling.yml delete mode 100644 .gitlab/tests/tracer.yml create mode 100644 .riot/requirements/196755b.txt create mode 100644 scripts/gen_gitlab_config.py create mode 100644 tests/README.md create mode 100644 tests/appsec/jobspec.yml create mode 100644 tests/ci_visibility/jobspec.yml create mode 100644 tests/contrib/jobspec.yml create mode 100644 tests/debugging/jobspec.yml create mode 100644 tests/jobspec.yml create mode 100644 tests/llmobs/jobspec.yml diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index ea6d3c9cb39..7beaabf9791 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -4,7 +4,7 @@ default_resource_class: &default_resource_class medium ubuntu_base_image: &ubuntu_base_img ubuntu-2004:2023.04.2 cimg_base_image: &cimg_base_image cimg/base:2022.08 python310_image: &python310_image cimg/python:3.10.12 -ddtrace_dev_image: &ddtrace_dev_image ghcr.io/datadog/dd-trace-py/testrunner@sha256:4c8afd048321e702f3605b4ae4d206fcd00e74bac708089cfe7f9c24383dc53b +ddtrace_dev_image: &ddtrace_dev_image ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 redis_image: &redis_image redis:4.0-alpine@sha256:3e99741f293147ff406657dda7644c2b88564b80a498cd00da8f905743449c9f memcached_image: &memcached_image memcached:1.5-alpine@sha256:48cb7207e3d34871893fa1628f3a4984375153e9942facf82e25935b0a633c8a cassandra_image: &cassandra_image cassandra:3.11.7@sha256:495e5752526f7e75d3ad85b6a6bbf3b79714321b17a44255a216c341e3baae11 diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index b1703e9c27e..f64c17bc413 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -11,7 +11,7 @@ jobs: validate: name: Check requirements lockfiles runs-on: ubuntu-latest - container: ghcr.io/datadog/dd-trace-py/testrunner@sha256:4c8afd048321e702f3605b4ae4d206fcd00e74bac708089cfe7f9c24383dc53b + container: ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 steps: - uses: actions/checkout@v4 with: diff --git a/.gitignore b/.gitignore index 92f055b78ce..72b95218d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,8 @@ template_venv/ cloned_venvs/ # CircleCI generated config .circleci/config.gen.yml +# GitLab CI generated config +.gitlab/*-gen.yml # Automatically generated fixtures tests/appsec/iast/fixtures/aspects/callers.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa04948a8e3..c3e44cc8e75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ stages: - package - - tests + - tests-gen + - tests-trigger - shared-pipeline - dogfood - benchmarks @@ -10,27 +11,37 @@ stages: variables: REPO_LANG: python # "python" is used everywhere rather than "py" - TESTRUNNER_IMAGE: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:ecc5741ff3e7c8a30363fcd9cca79a371dcea5b4 # CI_DEBUG_SERVICES: "true" -.testrunner: - image: $TESTRUNNER_IMAGE - # DEV: we have a larger pool of amd64 runners, prefer that over arm64 - tags: [ "arch:amd64" ] - timeout: 20m - before_script: - - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev - - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ - include: - remote: https://gitlab-templates.ddbuild.io/libdatadog/include/one-pipeline.yml - local: ".gitlab/services.yml" # Include early so others can use the definitions - local: ".gitlab/benchmarks.yml" - local: ".gitlab/package.yml" - - local: ".gitlab/tests.yml" - local: ".gitlab/macrobenchmarks.yml" - local: ".gitlab/dogfood.yml" - local: ".gitlab/release.yml" + - local: ".gitlab/testrunner.yml" + +tests-gen: + stage: tests-gen + extends: .testrunner + script: + - pip install riot==0.20.0 + - riot -v run --pass-env -s gitlab-gen-config -v + needs: [] + artifacts: + paths: + - .gitlab/tests-gen.yml + +run-tests-trigger: + stage: tests-trigger + needs: [ tests-gen ] + trigger: + include: + - artifact: .gitlab/tests-gen.yml + job: tests-gen + strategy: depend requirements_json_test: rules: diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 2f896637599..74d76bc0ae4 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -1,24 +1,3 @@ -build_base_venvs: - extends: .testrunner - stage: package - parallel: - matrix: - - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - variables: - CMAKE_BUILD_PARALLEL_LEVEL: 12 - PIP_VERBOSE: 1 - DD_PROFILING_NATIVE_TESTS: 1 - script: - - pip install riot==0.20.0 - - riot -P -v generate --python=$PYTHON_VERSION - artifacts: - name: venv_$PYTHON_VERSION - paths: - - .riot/venv_* - - ddtrace/**/*.so* - - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* - - ddtrace/internal/datadog/profiling/test/test_* - download_ddtrace_artifacts: image: registry.ddbuild.io/github-cli:v27480869-eafb11d-2.43.0 tags: [ "arch:amd64" ] diff --git a/.gitlab/testrunner.yml b/.gitlab/testrunner.yml new file mode 100644 index 00000000000..38e2111ec78 --- /dev/null +++ b/.gitlab/testrunner.yml @@ -0,0 +1,8 @@ +.testrunner: + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:7a2e802af76051f82d698919d2837eff18dbb48e + # DEV: we have a larger pool of amd64 runners, prefer that over arm64 + tags: [ "arch:amd64" ] + timeout: 20m + before_script: + - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev + - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 8d1b3d16d54..303333d9cb0 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -1,5 +1,23 @@ +stages: + - tests + variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s + REPO_LANG: python # "python" is used everywhere rather than "py" + # CI_DEBUG_SERVICES: "true" + + +.testrunner: + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:7a2e802af76051f82d698919d2837eff18dbb48e + # DEV: we have a larger pool of amd64 runners, prefer that over arm64 + tags: [ "arch:amd64" ] + timeout: 20m + before_script: + - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev + - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ + + +{{services.yml}} .test_base_hatch: extends: .testrunner @@ -22,6 +40,7 @@ variables: hatch run ${env}:test done + .test_base_hatch_snapshot: extends: .test_base_hatch services: @@ -32,6 +51,28 @@ variables: # agent at that host. Therefore setting this as a variable will cause recursive requests to the testagent - export DD_TRACE_AGENT_URL="http://testagent:9126" + +build_base_venvs: + extends: .testrunner + stage: tests + parallel: + matrix: + - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + variables: + CMAKE_BUILD_PARALLEL_LEVEL: 12 + PIP_VERBOSE: 1 + DD_PROFILING_NATIVE_TESTS: 1 + script: + - pip install riot==0.20.0 + - riot -P -v generate --python=$PYTHON_VERSION + artifacts: + name: venv_$PYTHON_VERSION + paths: + - .riot/venv_* + - ddtrace/**/*.so* + - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* + - ddtrace/internal/datadog/profiling/test/test_* + .test_base_riot: extends: .testrunner stage: tests @@ -71,13 +112,20 @@ variables: # DEV: All job variables get shared with services, setting `DD_TRACE_AGENT_URL` on the testagent will tell it to forward all requests to the # agent at that host. Therefore setting this as a variable will cause recursive requests to the testagent - export DD_TRACE_AGENT_URL="http://testagent:9126" + - ln -s "${CI_PROJECT_DIR}" "/root/project" + +slotscheck: + extends: .testrunner + stage: tests + needs: [] + script: + - hatch run slotscheck:_ + +conftests: + extends: .testrunner + stage: tests + needs: [] + script: + - hatch run meta-testing:meta-testing -include: - - local: ".gitlab/tests/appsec.yml" - - local: ".gitlab/tests/ci_visibility.yml" - - local: ".gitlab/tests/contrib.yml" - - local: ".gitlab/tests/core.yml" - - local: ".gitlab/tests/debugging.yml" - - local: ".gitlab/tests/llmobs.yml" - - local: ".gitlab/tests/tracer.yml" - - local: ".gitlab/tests/profiling.yml" +# Required jobs will appear here diff --git a/.gitlab/tests/appsec.yml b/.gitlab/tests/appsec.yml deleted file mode 100644 index 3cb1f360192..00000000000 --- a/.gitlab/tests/appsec.yml +++ /dev/null @@ -1,70 +0,0 @@ -appsec: - extends: .test_base_riot_snapshot - parallel: 6 - variables: - SUITE_NAME: "appsec$" - retry: 2 - -appsec iast: - extends: .test_base_riot_snapshot - parallel: 6 - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, postgres] - variables: - SUITE_NAME: "appsec_iast$" - TEST_POSTGRES_HOST: "postgres" - retry: 2 - timeout: 25m - -appsec iast tdd_propagation: - extends: .test_base_riot_snapshot - parallel: 2 - variables: - SUITE_NAME: "appsec_iast_tdd_propagation" - retry: 2 - -appsec iast memcheck: - extends: .test_base_riot_snapshot - parallel: 4 - variables: - SUITE_NAME: "appsec_iast_memcheck" - CI_DEBUG_TRACE: "true" - PYTEST_ADDOPTS: "-v -s" - retry: 2 - -appsec threats django: - extends: .test_base_hatch - parallel: 12 - variables: - SUITE_NAME: "appsec_threats_django" - retry: 2 - -appsec threats flask: - extends: .test_base_hatch - parallel: 10 - variables: - SUITE_NAME: "appsec_threats_flask" - retry: 2 - -appsec threats fastapi: - extends: .test_base_hatch - parallel: 9 - variables: - SUITE_NAME: "appsec_threats_fastapi" - retry: 2 - -appsec aggregated leak testing: - extends: .test_base_hatch - parallel: 3 - variables: - SUITE_NAME: "appsec_aggregated_leak_testing" - retry: 2 - timeout: 35m - -appsec iast native: - extends: .test_base_hatch - parallel: 6 - variables: - SUITE_NAME: "appsec_iast_native" - retry: 2 diff --git a/.gitlab/tests/ci_visibility.yml b/.gitlab/tests/ci_visibility.yml deleted file mode 100644 index ef3791bad93..00000000000 --- a/.gitlab/tests/ci_visibility.yml +++ /dev/null @@ -1,28 +0,0 @@ -ci_visibility: - extends: .test_base_riot_snapshot - parallel: 6 - variables: - SUITE_NAME: "ci_visibility" - -dd_coverage: - extends: .test_base_hatch_snapshot - parallel: 6 - variables: - SUITE_NAME: "dd_coverage" - -pytest: - extends: .test_base_riot_snapshot - parallel: 12 - variables: - SUITE_NAME: "pytest" - -pytest_v2: - extends: .test_base_hatch_snapshot - parallel: 10 - variables: - SUITE_NAME: "pytest" - -unittest: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "unittest" diff --git a/.gitlab/tests/contrib.yml b/.gitlab/tests/contrib.yml deleted file mode 100644 index b81feaa41d9..00000000000 --- a/.gitlab/tests/contrib.yml +++ /dev/null @@ -1,394 +0,0 @@ -algoliasearch: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "algoliasearch" - -aredis: - extends: .test_base_riot_snapshot - parallel: 3 - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - variables: - SUITE_NAME: "aredis$" - -asgi: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "asgi$" - -asynctest: - extends: .test_base_riot - parallel: 3 - variables: - SUITE_NAME: 'asynctest$' - -avro: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "avro" - -bottle: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "bottle" - -celery: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, rabbitmq] - - !reference [.services, redis] - variables: - SUITE_NAME: "celery" - LOG_LEVEL: DEBUG - SNAPSHOT_DIR: /snapshots - PORT: 9126 - SNAPSHOT_CI: 1 - DD_POOL_TRACE_CHECK_FAILURES: true - DD_DISABLE_ERROR_RESPONSES: true - ENABLED_CHECKS: trace_stall,meta_tracer_version_header,trace_content_length,trace_peer_service,trace_dd_service # disable flaky content length check - - -cherrypy: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "cherrypy" - -django: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, memcached] - - !reference [.services, redis] - - !reference [.services, postgres] - variables: - SUITE_NAME: "django($|_celery)" - TEST_POSTGRES_HOST: "postgres" - TEST_MEMCACHED_HOST: "memcached" - TEST_REDIS_HOST: "redis" - -django_hosts: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: 'django_hosts$' - -djangorestframework: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, memcached] - - !reference [.services, redis] - variables: - SUITE_NAME: "djangorestframework" - TEST_MEMCACHED_HOST: "memcached" - TEST_REDIS_HOST: "redis" - -dogpile_cache: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "dogpile_cache" - -dramatiq: - parallel: 2 - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - variables: - SUITE_NAME: "dramatiq" - TEST_REDIS_HOST: "redis" - -elasticsearch: - extends: .test_base_riot_snapshot - parallel: 17 - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, elasticsearch] - - !reference [.services, opensearch] - variables: - SUITE_NAME: "elasticsearch" - TEST_ELASTICSEARCH_HOST: "elasticsearch" - TEST_OPENSEARCH_HOST: "opensearch" - -falcon: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "falcon" - -fastapi: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "fastapi" - -flask: - extends: .test_base_riot_snapshot - parallel: 14 - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, memcached] - - !reference [.services, redis] - variables: - SUITE_NAME: "flask" - TEST_MEMCACHED_HOST: "memcached" - TEST_REDIS_HOST: "redis" - -gevent: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "gevent" - -graphene: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "graphene" - -graphql: - extends: .test_base_riot_snapshot - parallel: 6 - variables: - SUITE_NAME: "graphql" - -grpc: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "grpc" - -gunicorn: - extends: .test_base_riot_snapshot - parallel: 12 - variables: - SUITE_NAME: "gunicorn" - -jinja2: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "jinja2" - -kafka: - extends: .test_base_riot_snapshot - parallel: 4 - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, kafka] - variables: - SUITE_NAME: "kafka" - TEST_KAFKA_HOST: "kafka" - TEST_KAFKA_PORT: "29092" - -kombu: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, rabbitmq] - variables: - SUITE_NAME: "kombu" - -logbook: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "logbook" - -loguru: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "loguru" - -mako: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "mako" - -molten: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "molten" - -protobuf: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "protobuf" - -opentracer: - extends: .test_base_riot - variables: - SUITE_NAME: "opentracer" - -pylibmc: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, memcached] - variables: - SUITE_NAME: "pylibmc" - -pymemcache: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, memcached] - variables: - SUITE_NAME: "pymemcache" - -pymongo: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, mongo] - variables: - SUITE_NAME: "pymongo" - -pynamodb: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "pynamodb" - -pyodbc: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "pyodbc" - -pyramid: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "pyramid" - before_script: - - !reference [.test_base_riot_snapshot, before_script] - # This is because in the riotfile we specify a path to tests/contrib/pyramid/pserve_app - # in the lockfile it gets replaced with the absolute path of /root/project/tests/.../pserve_app - - ln -s "${CI_PROJECT_DIR}" "/root/project" - -redis: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - - !reference [.services, rediscluster] - parallel: 5 - variables: - SUITE_NAME: "^redis$" - -rediscluster: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - - !reference [.services, rediscluster] - variables: - SUITE_NAME: "rediscluster" - -rq: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - variables: - SUITE_NAME: "rq" - parallel: 2 - -sanic: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "sanic" - -snowflake: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "snowflake" - -sourcecode: - extends: .test_base_riot - variables: - SUITE_NAME: "sourcecode" - -starlette: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "starlette" - -stdlib: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: 'asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$' - -structlog: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "structlog" - -sourcecode: - extends: .test_base_riot - variables: - SUITE_NAME: "sourcecode" - -subprocess: - extends: .test_base_riot - variables: - SUITE_NAME: "subprocess" - -test_logging: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: 'test_logging' - -tornado: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "tornado" - -aiohttp: - extends: .test_base_riot_snapshot - parallel: 3 - variables: - SUITE_NAME: "aiohttp" - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, httpbin_local] - -requests: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "requests" - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, httpbin_local] - -httplib: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "httplib" - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, httpbin_local] - -httpx: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "httpx" - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, httpbin_local] - parallel: 2 - -urllib3: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, httpbin_local] - variables: - SUITE_NAME: "urllib3" - TEST_HTTPBIN_HOST: "httpbin-local" - TEST_HTTPBIN_PORT: "8001" - -wsgi: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "wsgi" - -yaaredis: - extends: .test_base_riot_snapshot - services: - - !reference [.test_base_riot_snapshot, services] - - !reference [.services, redis] - variables: - SUITE_NAME: "yaaredis$" diff --git a/.gitlab/tests/core.yml b/.gitlab/tests/core.yml deleted file mode 100644 index d283aa25981..00000000000 --- a/.gitlab/tests/core.yml +++ /dev/null @@ -1,49 +0,0 @@ -internal: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "internal" - -telemetry: - extends: .test_base_riot_snapshot - parallel: 4 - variables: - SUITE_NAME: "telemetry" - -integration-testagent: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "integration-snapshot*" - -integration-agent: - extends: .test_base_riot - variables: - SUITE_NAME: "integration-latest*" - -vendor: - extends: .test_base_riot - variables: - SUITE_NAME: "vendor" - -ddtracerun: - extends: .test_base_riot - parallel: 6 - services: - - !reference [.test_base_riot, services] - - !reference [.services, redis] - variables: - SUITE_NAME: "ddtracerun" - TEST_REDIS_HOST: "redis" - -slotscheck: - extends: .testrunner - stage: tests - needs: [] - script: - - hatch run slotscheck:_ - -conftests: - extends: .testrunner - stage: tests - needs: [] - script: - - hatch run meta-testing:meta-testing diff --git a/.gitlab/tests/debugging.yml b/.gitlab/tests/debugging.yml deleted file mode 100644 index 644073eb50e..00000000000 --- a/.gitlab/tests/debugging.yml +++ /dev/null @@ -1,5 +0,0 @@ -debugger: - extends: .test_base_riot - variables: - SUITE_NAME: "debugger" - diff --git a/.gitlab/tests/llmobs.yml b/.gitlab/tests/llmobs.yml deleted file mode 100644 index 7abae87537c..00000000000 --- a/.gitlab/tests/llmobs.yml +++ /dev/null @@ -1,28 +0,0 @@ - -llmobs: - extends: .test_base_riot - variables: - SUITE_NAME: "llmobs" - -openai: - extends: .test_base_riot_snapshot - parallel: 10 - variables: - SUITE_NAME: "openai" - -langchain: - extends: .test_base_riot_snapshot - parallel: 6 - variables: - SUITE_NAME: "langchain" - -anthropic: - extends: .test_base_riot_snapshot - parallel: 3 - variables: - SUITE_NAME: "anthropic" - -google_generativeai: - extends: .test_base_riot_snapshot - variables: - SUITE_NAME: "google_generativeai" diff --git a/.gitlab/tests/profiling.yml b/.gitlab/tests/profiling.yml deleted file mode 100644 index 9e7a6c8e2a1..00000000000 --- a/.gitlab/tests/profiling.yml +++ /dev/null @@ -1,7 +0,0 @@ -profiling: - extends: .test_base_riot - parallel: 20 - variables: - DD_TRACE_AGENT_URL: "" - SUITE_NAME: "profile$|profile-v2" - retry: 2 diff --git a/.gitlab/tests/tracer.yml b/.gitlab/tests/tracer.yml deleted file mode 100644 index 85898157c04..00000000000 --- a/.gitlab/tests/tracer.yml +++ /dev/null @@ -1,7 +0,0 @@ -tracer: - extends: .test_base_riot - parallel: 9 - variables: - DD_TRACE_AGENT_URL: "http://localhost:8126" - SUITE_NAME: "tracer" - retry: 2 \ No newline at end of file diff --git a/.riot/requirements/196755b.txt b/.riot/requirements/196755b.txt new file mode 100644 index 00000000000..250298e3848 --- /dev/null +++ b/.riot/requirements/196755b.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/196755b.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +ruamel-yaml==0.18.6 +ruamel-yaml-clib==0.2.8 +sortedcontainers==2.4.0 diff --git a/docker-compose.yml b/docker-compose.yml index 6b25f80cf75..af3e559e7f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,7 +152,7 @@ services: - "127.0.0.1:5433:5433" testrunner: - image: ghcr.io/datadog/dd-trace-py/testrunner@sha256:4c8afd048321e702f3605b4ae4d206fcd00e74bac708089cfe7f9c24383dc53b + image: ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 command: bash environment: - TOX_SKIP_DIST=True diff --git a/riotfile.py b/riotfile.py index c31945aedf3..4de6347f85a 100644 --- a/riotfile.py +++ b/riotfile.py @@ -120,6 +120,14 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "ruamel.yaml": latest, }, ), + Venv( + name="gitlab-gen-config", + command="python scripts/gen_gitlab_config.py {cmdargs}", + pys=["3"], + pkgs={ + "ruamel.yaml": latest, + }, + ), Venv( name="appsec", pys=select_pys(), diff --git a/scripts/gen_circleci_config.py b/scripts/gen_circleci_config.py index 1254081fd93..4afbec47cc1 100644 --- a/scripts/gen_circleci_config.py +++ b/scripts/gen_circleci_config.py @@ -8,8 +8,9 @@ import typing as t -def gen_required_suites(template: dict, git_selections: list) -> None: +def gen_required_suites(template: dict) -> None: """Generate the list of test suites that need to be run.""" + from needs_testrun import extract_git_commit_selections from needs_testrun import for_each_testrun_needed as fetn from suitespec import get_suites @@ -18,7 +19,9 @@ def gen_required_suites(template: dict, git_selections: list) -> None: required_suites = template["requires_tests"]["requires"] = [] fetn( - suites=sorted(suites & jobs), action=lambda suite: required_suites.append(suite), git_selections=git_selections + suites=sorted(suites & jobs), + action=lambda suite: required_suites.append(suite), + git_selections=extract_git_commit_selections(os.getenv("GIT_COMMIT_DESC", "")), ) if not required_suites: @@ -98,15 +101,6 @@ def gen_build_docs(template: dict) -> None: template["workflows"]["test"]["jobs"].append({"build_docs": template["requires_pre_check"]}) -def extract_git_commit_selections(git_commit_message: str) -> dict: - """Extract the selected suites from git commit message.""" - suites = set() - for token in git_commit_message.split(): - if token.lower().startswith("circleci:"): - suites.update(token[len("circleci:") :].lower().split(",")) - return list(sorted(suites)) - - # ----------------------------------------------------------------------------- # The code below is the boilerplate that makes the script work. There is @@ -142,7 +136,6 @@ def extract_git_commit_selections(git_commit_message: str) -> dict: with YAML(output=CONFIG_GEN_FILE) as yaml: LOGGER.info("Loading configuration template from %s", CONFIG_TEMPLATE_FILE) config = yaml.load(CONFIG_TEMPLATE_FILE) - git_commit_selections = extract_git_commit_selections(os.getenv("GIT_COMMIT_DESC")) has_error = False LOGGER.info("Configuration generation steps:") @@ -151,10 +144,7 @@ def extract_git_commit_selections(git_commit_message: str) -> dict: desc = func.__doc__.splitlines()[0] try: start = time() - if name == "gen_required_suites": - func(config, git_commit_selections) - else: - func(config) + func(config) end = time() LOGGER.info("- %s: %s [took %dms]", name, desc, int((end - start) / 1e6)) except Exception as e: diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py new file mode 100644 index 00000000000..2c7e5a5b6c2 --- /dev/null +++ b/scripts/gen_gitlab_config.py @@ -0,0 +1,153 @@ +# This script is used to generate the GitLab dynamic config file in +# .gitlab/tests.yml. +# +# To add new configuration manipulations that are based on top of the template +# file in .gitlab/tests.yml, add a function named gen_ to this +# file. The function will be called automatically when this script is run. + +from dataclasses import dataclass +import typing as t + + +@dataclass +class JobSpec: + name: str + runner: str + is_snapshot: bool = False + services: t.Optional[t.Set[str]] = None + env: t.Optional[t.Dict[str, str]] = None + parallelism: t.Optional[int] = None + retry: t.Optional[int] = None + timeout: t.Optional[int] = None + + def __str__(self) -> str: + lines = [] + base = f".test_base_{self.runner}" + if self.is_snapshot: + base += "_snapshot" + + lines.append(f"{self.name}:") + lines.append(f" extends: {base}") + + if self.services: + lines.append(" services:") + + _services = [f"!reference [.services, {_}]" for _ in self.services] + if self.is_snapshot: + _services.insert(0, f"!reference [{base}, services]") + + for service in _services: + lines.append(f" - {service}") + + if self.env: + lines.append(" variables:") + for key, value in self.env.items(): + lines.append(f" {key}: {value}") + + if self.parallelism is not None: + lines.append(f" parallel: {self.parallelism}") + + if self.retry is not None: + lines.append(f" retry: {self.retry}") + + if self.timeout is not None: + lines.append(f" timeout: {self.timeout}") + + return "\n".join(lines) + + +def collect_jobspecs() -> dict: + # Recursively search for jobspec.yml in TESTS + jobspecs = {} + for js in TESTS.rglob("jobspec.yml"): + with YAML() as yaml: + LOGGER.info("Loading jobspecs from %s", js) + jobspecs.update(yaml.load(js)) + + return jobspecs + + +def gen_required_suites() -> None: + """Generate the list of test suites that need to be run.""" + from needs_testrun import extract_git_commit_selections + from needs_testrun import for_each_testrun_needed as fetn + from suitespec import get_suites + + jobspecs = collect_jobspecs() + + suites = get_suites() + required_suites = [] + + for suite in suites: + if suite not in jobspecs: + print(f"WARNING: Suite {suite} has no jobspec", file=sys.stderr) + continue + + for job in jobspecs: + if job not in suites: + print(f"WARNING: Job {job} has no suitespec", file=sys.stderr) + continue + + fetn( + suites=sorted(suites), + action=lambda suite: required_suites.append(suite), + git_selections=extract_git_commit_selections(os.getenv("CI_COMMIT_MESSAGE", "")), + ) + + # Copy the template file + (GITLAB / "tests-gen.yml").write_text( + (GITLAB / "tests.yml").read_text().replace(r"{{services.yml}}", (GITLAB / "services.yml").read_text()) + ) + + # Generate the list of suites to run + with (GITLAB / "tests-gen.yml").open("a") as f: + for suite in required_suites: + if suite not in jobspecs: + print(f"Suite {suite} not found in jobspec", file=sys.stderr) + continue + + print(str(JobSpec(suite, **jobspecs[suite])), file=f) + + +# ----------------------------------------------------------------------------- + +# The code below is the boilerplate that makes the script work. There is +# generally no reason to modify it. + +import logging # noqa +import os # noqa +import sys # noqa +from argparse import ArgumentParser # noqa +from pathlib import Path # noqa +from time import monotonic_ns as time # noqa + +from ruamel.yaml import YAML # noqa + +logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s") +LOGGER = logging.getLogger(__name__) + +argp = ArgumentParser() +argp.add_argument("--verbose", "-v", action="store_true", help="Verbose output") +args = argp.parse_args() +if args.verbose: + LOGGER.setLevel(logging.INFO) + +ROOT = Path(__file__).parents[1] +GITLAB = ROOT / ".gitlab" +TESTS = ROOT / "tests" +# Make the scripts and tests folders available for importing. +sys.path.append(str(ROOT / "scripts")) +sys.path.append(str(ROOT / "tests")) + +LOGGER.info("Configuration generation steps:") +for name, func in dict(globals()).items(): + if name.startswith("gen_"): + desc = func.__doc__.splitlines()[0] + try: + start = time() + func() + end = time() + LOGGER.info("- %s: %s [took %dms]", name, desc, int((end - start) / 1e6)) + except Exception as e: + LOGGER.error("- %s: %s [reason: %s]", name, desc, str(e), exc_info=True) + has_error = True diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index 0ea8235622b..ecac780e328 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -12,6 +12,7 @@ from subprocess import check_output import sys import typing as t +from urllib.parse import urlencode from urllib.request import Request from urllib.request import urlopen @@ -68,6 +69,45 @@ def get_latest_commit_message() -> str: return "" +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") +if not GITHUB_TOKEN: + try: + GITHUB_TOKEN = ( + check_output( + [ + "aws", + "ssm", + "get-parameter", + "--region", + "us-east-1", + "--name", + f'ci.{os.environ["CI_PROJECT_NAME"]}.gh_token', + "--with-decryption", + "--query", + "Parameter.Value", + "--output=text", + ] + ) + .decode("utf-8") + .strip() + ) + LOGGER.info("GitHub token retrieved from SSM") + except Exception: + LOGGER.warning("No GitHub token available. Changes may not be detected accurately.", exc_info=True) +else: + LOGGER.info("GitHub token retrieved from environment") + + +def github_api(path: str, query: t.Optional[dict] = None) -> t.Any: + url = f"https://api.github.com/repos/datadog/dd-trace-py{path}" + headers = {"Accept": "application/vnd.github+json"} + if GITHUB_TOKEN: + headers["Authorization"] = f"Bearer {GITHUB_TOKEN}" + if query is not None: + url += "?" + urlencode(query) + return json.load(urlopen(Request(url, headers=headers))) + + @cache def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str]: """Get the files changed in a PR @@ -85,9 +125,7 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] files = set() try: for page in count(1): - url = f"https://api.github.com/repos/datadog/dd-trace-py/pulls/{pr_number}/files?page={page}" - headers = {"Accept": "application/vnd.github+json"} - result = {_["filename"] for _ in json.load(urlopen(Request(url, headers=headers)))} + result = {_["filename"] for _ in github_api(f"/pulls/{pr_number}/files", {"page": page})} if not result: return files files |= result @@ -151,35 +189,35 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo return bool(matches) -def _get_pr_number(): +def _get_pr_number() -> int: + # CircleCI number = os.environ.get("CIRCLE_PR_NUMBER") - if not number: - pr_url = os.environ.get("CIRCLE_PULL_REQUEST", "") - number = pr_url.split("/")[-1] - try: + if number is not None: return int(number) - except ValueError: - return 0 + pr_url = os.environ.get("CIRCLE_PULL_REQUEST") + if pr_url is not None: + return int(pr_url.split("/")[-1]) -def for_each_testrun_needed(suites: t.List[str], action: t.Callable[[str], None], git_selections: t.List[str]): - # Used in CircleCI config - pr_number = _get_pr_number() + # GitLab + ref_name = os.environ.get("CI_COMMIT_REF_NAME") + if ref_name is not None: + return int(github_api("/pulls", {"head": f"datadog:{ref_name}"})[0]["number"]) - for suite in suites: - if pr_number <= 0: - # If we don't have a valid PR number we run all tests - action(suite) - continue + raise RuntimeError("Could not determine PR number") - if any(x in git_selections for x in ("all", suite)): - # If "all" or current suite is annotated - # in git commit we run the suite - action(suite) - continue - needs_run = needs_testrun(suite, pr_number) - if needs_run: +def for_each_testrun_needed(suites: t.List[str], action: t.Callable[[str], None], git_selections: t.Set[str]): + try: + pr_number = _get_pr_number() + except Exception: + pr_number = None + + for suite in suites: + # If we don't have a valid PR number we run all tests + # or "all" or current suite is annotated in git commit we run the suite + # or the suite needs to be run based on the changed files + if pr_number is None or (git_selections & {"all", suite}) or needs_testrun(suite, pr_number): action(suite) @@ -192,6 +230,15 @@ def pr_matches_patterns(patterns: t.Set[str]) -> bool: return bool([_ for p in patterns for _ in fnmatch.filter(changed_files, p)]) +def extract_git_commit_selections(git_commit_message: str) -> t.Set[str]: + """Extract the selected suites from git commit message.""" + suites = set() + for token in git_commit_message.split(): + if token.lower().startswith("circleci:"): + suites.update(token[len("circleci:") :].lower().split(",")) + return suites + + def main() -> bool: argp = ArgumentParser() diff --git a/tests/.suitespec.json b/tests/.suitespec.json index 773e41b366d..1e85eff0ffa 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -23,7 +23,10 @@ "tests/subprocesstest.py", "tests/wait-for-services.py", "tests/webclient.py", - "tests/test_module/*" + "tests/test_module/*", + ".gitlab-ci.yml", + ".gitlab/*", + "tests/jobspec.yml" ], "core": [ "ddtrace/internal/__init__.py", @@ -133,7 +136,8 @@ "ddtrace/settings/_database_monitoring.py", "tests/contrib/patch.py", "tests/contrib/config.py", - "tests/contrib/__init__.py" + "tests/contrib/__init__.py", + "tests/contrib/jobspec.yml" ], "redis": [ "ddtrace/contrib/rediscluster/*", @@ -788,12 +792,7 @@ "@bootstrap", "@core", "@profiling", - "tests/profiling/*" - ], - "profile-v2": [ - "@bootstrap", - "@core", - "@profiling", + "tests/profiling/*", "tests/profiling_v2/*" ], "vendor": [ @@ -835,45 +834,6 @@ "@loguru", "tests/contrib/loguru/*" ], - "asyncio": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@asyncio" - ], - "futures": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@futures" - ], - "sqlite3": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@sqlite3", - "@dbapi" - ], - "dbapi": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@appsec", - "tests/contrib/shared_tests.py" - ], - "dbapi_async": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "tests/contrib/shared_tests_async.py" - ], "asyncpg": [ "@bootstrap", "@core", diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000000..20475da6965 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,38 @@ +# Dynamic Job Runs + +This repository makes use of dynamic features of CI providers to run only the +jobs that are necessary for the changes in the pull request. This is done by +giving a logical description of the test suites in terms of _components_. A +component is a logical grouping of tests that can be run independently of other +components. For example, a component could be a test suite for a specific +package, or a test suite for a specific feature, e.g. the tracer, the profiler, +etc... . Components may depend on each other, provide that the resulting +dependency graph is acyclic. For example, the library has a _core_ component +on which most, if not all, other components depend. When a change is made to the +core component, all other components depending on it must be re-tested. This +logical description is contained in the `/tests/.suitespec.json` file. + +The jobs to be run are defined in `jobspec.yml` files under the `/tests` +sub-tree. Each entry has the name of a suite from the suitespec file, and +defines the parameters for the runner image. The general structure of a job spec +is + +```yaml +suite_name: # A suite name from the .suitespec.json file + runner: # The runner image to use (riot | hatch) + is_snapshot: # Whether this is a snapshot test run (optional, defaults to false) + services: # Any services to spawn for the tests (e.g. redis, optional) + env: # Environment variables to set for the tests + SUITE_NAME: # The suite pattern from the riotfile|hatch env names (mandatory) + parallelism: # The parallel degree of the job (optional, defaults to 1) + retry: # The number of retries for the job (optional) + timeout: # The timeout for the job (optional) +``` + +To create a new job for a new test suite, create a new suite entry in the +`.suitespec.json` file and declare the file patterns that should trigger it. +Then create a new job in a `jobspec.yml` file under the `/tests` sub-tree with +the format described above, matching the name of the suite in the +`.suitespec.json` just created. The `SUITE_NAME` environment variable must be +a regular expression matching at least one riotfile environment (for the riot +runner) or a hatch environment (for the hatch runner). diff --git a/tests/appsec/jobspec.yml b/tests/appsec/jobspec.yml new file mode 100644 index 00000000000..bea6d7b77ec --- /dev/null +++ b/tests/appsec/jobspec.yml @@ -0,0 +1,65 @@ +appsec: + runner: riot + is_snapshot: true + env: + SUITE_NAME: "appsec$" + parallelism: 6 + retry: 2 +appsec_iast: + runner: riot + is_snapshot: true + services: + - postgres + env: + SUITE_NAME: "appsec_iast$" + TEST_POSTGRES_HOST: postgres + parallelism: 6 + retry: 2 + timeout: 25m +appsec_iast_tdd_propagation: + runner: riot + is_snapshot: true + env: + SUITE_NAME: appsec_iast_tdd_propagation + parallelism: 2 + retry: 2 +appsec_iast_memcheck: + runner: riot + is_snapshot: true + env: + SUITE_NAME: appsec_iast_memcheck + CI_DEBUG_TRACE: "true" + PYTEST_ADDOPTS: "-v -s" + parallelism: 4 + retry: 2 +appsec_threats_django: + runner: hatch + env: + SUITE_NAME: appsec_threats_django + parallelism: 12 + retry: 2 +appsec_threats_flask: + runner: hatch + env: + SUITE_NAME: appsec_threats_flask + parallelism: 10 + retry: 2 +appsec_threats_fastapi: + runner: hatch + env: + SUITE_NAME: appsec_threats_fastapi + parallelism: 9 + retry: 2 +appsec_aggregated_leak_testing: + runner: hatch + env: + SUITE_NAME: appsec_aggregated_leak_testing + parallelism: 3 + retry: 2 + timeout: 35m +appsec_iast_native: + runner: hatch + env: + SUITE_NAME: appsec_iast_native + parallelism: 6 + retry: 2 diff --git a/tests/ci_visibility/jobspec.yml b/tests/ci_visibility/jobspec.yml new file mode 100644 index 00000000000..fc08931eda9 --- /dev/null +++ b/tests/ci_visibility/jobspec.yml @@ -0,0 +1,29 @@ +ci_visibility: + runner: riot + is_snapshot: true + env: + SUITE_NAME: ci_visibility + parallelism: 4 +dd_coverage: + runner: hatch + is_snapshot: true + env: + SUITE_NAME: dd_coverage + parallelism: 6 +pytest: + runner: riot + is_snapshot: true + env: + SUITE_NAME: pytest + parallelism: 12 +pytest_v2: + runner: hatch + is_snapshot: true + env: + SUITE_NAME: pytest_plugin_v2 + parallelism: 10 +unittest: + runner: riot + is_snapshot: true + env: + SUITE_NAME: unittest diff --git a/tests/contrib/jobspec.yml b/tests/contrib/jobspec.yml new file mode 100644 index 00000000000..32f05864383 --- /dev/null +++ b/tests/contrib/jobspec.yml @@ -0,0 +1,360 @@ +aiohttp: + runner: riot + is_snapshot: true + services: + - httpbin_local + env: + SUITE_NAME: aiohttp + parallelism: 3 +asgi: + runner: riot + is_snapshot: true + env: + SUITE_NAME: "asgi$" +tornado: + runner: riot + is_snapshot: true + env: + SUITE_NAME: tornado +bottle: + runner: riot + is_snapshot: true + env: + SUITE_NAME: bottle + parallelism: 1 +celery: + runner: riot + is_snapshot: true + services: + - rabbitmq + - redis + env: + SUITE_NAME: celery + LOG_LEVEL: DEBUG + SNAPSHOT_DIR: /snapshots + PORT: 9126 + SNAPSHOT_CI: 1 + DD_POOL_TRACE_CHECK_FAILURES: true + DD_DISABLE_ERROR_RESPONSES: true + ENABLED_CHECKS: trace_stall,meta_tracer_version_header,trace_content_length,trace_peer_service,trace_dd_service # disable flaky content length check +cherrypy: + runner: riot + is_snapshot: true + env: + SUITE_NAME: cherrypy +dogpile_cache: + runner: riot + is_snapshot: true + env: + SUITE_NAME: dogpile_cache +elasticsearch: + runner: riot + is_snapshot: true + services: + - opensearch + - elasticsearch + env: + SUITE_NAME: elasticsearch + TEST_ELASTICSEARCH_HOST: "elasticsearch" + TEST_OPENSEARCH_HOST: "opensearch" + parallelism: 17 +falcon: + runner: riot + is_snapshot: true + env: + SUITE_NAME: falcon +django: + runner: riot + is_snapshot: true + services: + - postgres + - memcached + - redis + env: + SUITE_NAME: "django($|_celery)" + TEST_POSTGRES_HOST: "postgres" + TEST_MEMCACHED_HOST: "memcached" + TEST_REDIS_HOST: "redis" +django_hosts: + runner: riot + is_snapshot: true + env: + SUITE_NAME: "django_hosts$" +djangorestframework: + runner: riot + is_snapshot: true + services: + - memcached + - redis + env: + SUITE_NAME: djangorestframework + TEST_MEMCACHED_HOST: memcached + TEST_REDIS_HOST: redis +dramatiq: + runner: riot + is_snapshot: true + services: + - redis + env: + SUITE_NAME: dramatiq + TEST_REDIS_HOST: redis + parallelism: 2 +fastapi: + runner: riot + is_snapshot: true + env: + SUITE_NAME: fastapi +flask: + runner: riot + is_snapshot: true + services: + - memcached + - redis + env: + SUITE_NAME: flask + TEST_MEMCACHED_HOST: memcached + TEST_REDIS_HOST: redis + parallelism: 14 +gevent: + runner: riot + is_snapshot: true + env: + SUITE_NAME: gevent +graphene: + runner: riot + is_snapshot: true + env: + SUITE_NAME: graphene +graphql: + runner: riot + is_snapshot: true + env: + SUITE_NAME: graphql + parallelism: 6 +grpc: + runner: riot + is_snapshot: true + env: + SUITE_NAME: grpc +gunicorn: + runner: riot + is_snapshot: true + env: + SUITE_NAME: gunicorn + parallelism: 12 +httplib: + runner: riot + is_snapshot: true + services: + - httpbin_local + env: + SUITE_NAME: httplib +httpx: + runner: riot + is_snapshot: true + services: + - httpbin_local + env: + SUITE_NAME: httpx + parallelism: 2 +molten: + runner: riot + is_snapshot: true + env: + SUITE_NAME: molten +pylibmc: + runner: riot + is_snapshot: true + services: + - memcached + env: + SUITE_NAME: pylibmc +asynctest: + runner: riot + env: + SUITE_NAME: "asynctest$" + parallelism: 3 +pymemcache: + runner: riot + is_snapshot: true + services: + - memcached + env: + SUITE_NAME: pymemcache +pymongo: + runner: riot + is_snapshot: true + services: + - mongo + env: + SUITE_NAME: pymongo +pynamodb: + runner: riot + is_snapshot: true + env: + SUITE_NAME: pynamodb +pyodbc: + runner: riot + is_snapshot: true + env: + SUITE_NAME: pyodbc +pyramid: + runner: riot + is_snapshot: true + env: + SUITE_NAME: pyramid +requests: + runner: riot + is_snapshot: true + services: + - httpbin_local + env: + SUITE_NAME: requests +sanic: + runner: riot + is_snapshot: true + env: + SUITE_NAME: sanic +snowflake: + runner: riot + is_snapshot: true + env: + SUITE_NAME: snowflake +starlette: + runner: riot + is_snapshot: true + env: + SUITE_NAME: starlette +structlog: + runner: riot + is_snapshot: true + env: + SUITE_NAME: structlog +aredis: + runner: riot + is_snapshot: true + services: + - redis + env: + SUITE_NAME: "aredis$" + parallelism: 3 +yaaredis: + runner: riot + is_snapshot: true + services: + - redis + env: + SUITE_NAME: "yaaredis$" +redis: + runner: riot + is_snapshot: true + services: + - rediscluster + - redis + env: + SUITE_NAME: "^redis$" + parallelism: 5 +rediscluster: + runner: riot + is_snapshot: true + services: + - redis + - rediscluster + env: + SUITE_NAME: rediscluster +rq: + runner: riot + is_snapshot: true + services: + - redis + env: + SUITE_NAME: rq + parallelism: 2 +urllib3: + runner: riot + is_snapshot: true + services: + - httpbin_local + env: + SUITE_NAME: urllib3 + TEST_HTTPBIN_HOST: "httpbin-local" + TEST_HTTPBIN_PORT: "8001" +wsgi: + runner: riot + is_snapshot: true + env: + SUITE_NAME: wsgi +kafka: + runner: riot + is_snapshot: true + services: + - kafka + env: + SUITE_NAME: kafka + TEST_KAFKA_HOST: "kafka" + TEST_KAFKA_PORT: "29092" + parallelism: 4 +kombu: + runner: riot + is_snapshot: true + services: + - rabbitmq + env: + SUITE_NAME: kombu +jinja2: + runner: riot + is_snapshot: true + env: + SUITE_NAME: jinja2 +mako: + runner: riot + is_snapshot: true + env: + SUITE_NAME: mako +algoliasearch: + runner: riot + is_snapshot: true + env: + SUITE_NAME: algoliasearch +logbook: + runner: riot + is_snapshot: true + env: + SUITE_NAME: logbook +loguru: + runner: riot + is_snapshot: true + env: + SUITE_NAME: loguru +stdlib: + runner: riot + is_snapshot: true + env: + SUITE_NAME: 'asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$' +test_logging: + runner: riot + is_snapshot: true + env: + SUITE_NAME: test_logging +subprocess: + runner: riot + env: + SUITE_NAME: subprocess +sourcecode: + runner: riot + env: + SUITE_NAME: sourcecode +opentracer: + runner: riot + env: + SUITE_NAME: opentracer +protobuf: + runner: riot + is_snapshot: true + env: + SUITE_NAME: protobuf +avro: + runner: riot + is_snapshot: true + env: + SUITE_NAME: avro diff --git a/tests/debugging/jobspec.yml b/tests/debugging/jobspec.yml new file mode 100644 index 00000000000..0e306aa642b --- /dev/null +++ b/tests/debugging/jobspec.yml @@ -0,0 +1,5 @@ +debugger: + runner: riot + env: + SUITE_NAME: debugger + parallelism: 2 diff --git a/tests/jobspec.yml b/tests/jobspec.yml new file mode 100644 index 00000000000..0005344a045 --- /dev/null +++ b/tests/jobspec.yml @@ -0,0 +1,46 @@ +internal: + runner: riot + is_snapshot: true + env: + SUITE_NAME: internal +tracer: + runner: riot + env: + SUITE_NAME: tracer + DD_TRACE_AGENT_URL: "http://localhost:8126" + parallelism: 9 + retry: 2 +telemetry: + runner: riot + is_snapshot: true + env: + SUITE_NAME: telemetry + parallelism: 6 +profile: + runner: riot + env: + SUITE_NAME: "profile$|profile-v2" + DD_TRACE_AGENT_URL: "" + parallelism: 20 + retry: 2 +integration_testagent: + runner: riot + is_snapshot: true + env: + SUITE_NAME: "integration-snapshot*" +integration_agent: + runner: riot + env: + SUITE_NAME: "integration-latest*" +vendor: + runner: riot + env: + SUITE_NAME: vendor + parallelism: 1 +ddtracerun: + runner: riot + services: + - redis + env: + SUITE_NAME: ddtracerun + parallelism: 6 diff --git a/tests/llmobs/jobspec.yml b/tests/llmobs/jobspec.yml new file mode 100644 index 00000000000..f039ce8302c --- /dev/null +++ b/tests/llmobs/jobspec.yml @@ -0,0 +1,27 @@ +llmobs: + runner: riot + env: + SUITE_NAME: llmobs +openai: + runner: riot + is_snapshot: true + env: + SUITE_NAME: openai + parallelism: 10 +langchain: + runner: riot + is_snapshot: true + env: + SUITE_NAME: langchain + parallelism: 6 +anthropic: + runner: riot + is_snapshot: true + env: + SUITE_NAME: anthropic + parallelism: 3 +google_generativeai: + runner: riot + is_snapshot: true + env: + SUITE_NAME: google_generativeai From 5feb28bf218e8a735f82989f9fa3f056506d3d0f Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Wed, 13 Nov 2024 17:07:19 -0500 Subject: [PATCH 152/372] chore(llmobs): add tracer version field to llmobs span payload (#11386) ## Checklist This PR adds a `_dd.tracer_version` field to the span payload at the LLMObs writer level. This is only meant to be used internally by the LLMObs team. - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 0b79176b51a..6496de96cfe 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -11,6 +11,7 @@ from typing import TypedDict except ImportError: from typing_extensions import TypedDict +import ddtrace from ddtrace import config from ddtrace.internal import agent from ddtrace.internal import forksafe @@ -204,7 +205,7 @@ def encode(self): return None, 0 events = self._buffer self._init_buffer() - data = {"_dd.stage": "raw", "event_type": "span", "spans": events} + data = {"_dd.stage": "raw", "_dd.tracer_version": ddtrace.__version__, "event_type": "span", "spans": events} try: enc_llm_events = json.dumps(data) logger.debug("encode %d LLMObs span events to be sent", len(events)) From a7da5e43260e8180841d7d15159abeb190eeea43 Mon Sep 17 00:00:00 2001 From: wantsui Date: Wed, 13 Nov 2024 19:56:17 -0500 Subject: [PATCH 153/372] chore: remove compat.stringify usage from gunicorn test (#11341) --- tests/contrib/gunicorn/test_gunicorn.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/contrib/gunicorn/test_gunicorn.py b/tests/contrib/gunicorn/test_gunicorn.py index 1a1d7367a01..5906e5bfbf3 100644 --- a/tests/contrib/gunicorn/test_gunicorn.py +++ b/tests/contrib/gunicorn/test_gunicorn.py @@ -10,9 +10,7 @@ import pytest -from ddtrace.internal import compat from ddtrace.internal.utils.retry import RetryError # noqa:F401 -from tests.utils import flaky from tests.utils import snapshot_context from tests.webclient import Client @@ -111,7 +109,7 @@ def post_worker_init(worker): def gunicorn_server(gunicorn_server_settings, tmp_path): cfg_file = tmp_path / "gunicorn.conf.py" cfg = build_config_file(gunicorn_server_settings) - cfg_file.write_text(compat.stringify(cfg)) + cfg_file.write_text(cfg) cmd = [] if gunicorn_server_settings.use_ddtracerun: cmd = ["ddtrace-run"] @@ -167,7 +165,6 @@ def gunicorn_server(gunicorn_server_settings, tmp_path): ) -@flaky(until=1706677200) @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Gunicorn is only supported up to 3.10") def test_no_known_errors_occur(tmp_path): for gunicorn_server_settings in [ @@ -185,7 +182,6 @@ def test_no_known_errors_occur(tmp_path): assert payload["profiler"]["is_active"] is True -@flaky(until=1706677200) @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Gunicorn is only supported up to 3.10") def test_span_schematization(tmp_path): for schema_version in [None, "v0", "v1"]: From a5bb7e3590293d51de500d220962c87e3beb0247 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 13 Nov 2024 20:09:24 -0500 Subject: [PATCH 154/372] chore(profiling): fix flaky gunicorn test (#11393) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling/collector/pprof_utils.py | 2 +- tests/profiling_v2/test_gunicorn.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/profiling/collector/pprof_utils.py b/tests/profiling/collector/pprof_utils.py index 59f2795b6d6..95f865a4cdf 100644 --- a/tests/profiling/collector/pprof_utils.py +++ b/tests/profiling/collector/pprof_utils.py @@ -281,7 +281,7 @@ def assert_sample_has_locations(profile, sample, expected_locations: Optional[Li if expected_locations_idx < len(expected_locations): if ( function_name.endswith(expected_locations[expected_locations_idx].function_name) - and filename == expected_locations[expected_locations_idx].filename + and re.fullmatch(expected_locations[expected_locations_idx].filename, filename) and line_no == expected_locations[expected_locations_idx].line_no ): expected_locations_idx += 1 diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index 79c33777a59..ef97d9683f8 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -93,15 +93,17 @@ def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") assert len(samples) > 0 + # DEV: somehow the filename is reported as either __init__.py or gunicorn-app.py + # when run on GitLab CI. We need to match either of these two. + filename_regex = r"^(?:__init__\.py|gunicorn-app\.py)$" + + expected_location = pprof_utils.StackLocation(function_name="fib", filename=filename_regex, line_no=8) + pprof_utils.assert_profile_has_sample( profile, samples=samples, - expected_sample=pprof_utils.StackEvent( - locations=[ - pprof_utils.StackLocation(function_name="fib", filename="gunicorn-app.py", line_no=8), - pprof_utils.StackLocation(function_name="fib", filename="gunicorn-app.py", line_no=8), - ] - ), + # DEV: we expect multiple locations as fibonacci is recursive + expected_sample=pprof_utils.StackEvent(locations=[expected_location, expected_location]), ) From 6dd6349909edaac5a5811dbc19b3e5d07138933a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 13 Nov 2024 21:26:48 -0500 Subject: [PATCH 155/372] chore(profiling): stack collector tests for libdd and stack v2 (#11390) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling_v2/collector/test_stack.py | 164 +++++++++++++++++++-- 1 file changed, 149 insertions(+), 15 deletions(-) diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index d7e59a9266c..5d9007248bc 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -16,6 +16,15 @@ from tests.profiling.collector import pprof_utils +# Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 +# https://github.com/python/cpython/issues/117983 +# The fix was not backported to 3.11. The fix was first released in 3.12.5 for +# Python 3.12. Tested with Python 3.11.8 and 3.12.5 to confirm the issue. +TESTING_GEVENT = os.getenv("DD_PROFILE_TEST_GEVENT", False) and ( + sys.version_info < (3, 11, 9) or sys.version_info >= (3, 12, 5) +) + + @pytest.mark.parametrize("stack_v2_enabled", [True, False]) def test_stack_locations(stack_v2_enabled, tmp_path): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: @@ -30,7 +39,7 @@ def test_stack_locations(stack_v2_enabled, tmp_path): ddup.start() def baz(): - time.sleep(0.01) + time.sleep(0.1) def bar(): baz() @@ -39,7 +48,7 @@ def foo(): bar() with stack.StackCollector(None, _stack_collector_v2_enabled=stack_v2_enabled): - for _ in range(5): + for _ in range(10): foo() ddup.upload() @@ -100,8 +109,8 @@ def test_push_span(stack_v2_enabled, tmp_path): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id local_root_span_id = span._local_root.span_id - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) ddup.upload() profile = pprof_utils.parse_profile(output_filename) @@ -140,8 +149,8 @@ def test_push_span_unregister_thread(tmp_path, monkeypatch): span_type = ext.SpanTypes.WEB def target_fun(): - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) with stack.StackCollector( None, @@ -205,8 +214,8 @@ def test_push_non_web_span(stack_v2_enabled, tmp_path): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id local_root_span_id = span._local_root.span_id - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) ddup.upload() profile = pprof_utils.parse_profile(output_filename) @@ -255,8 +264,8 @@ def test_push_span_none_span_type(stack_v2_enabled, tmp_path): with tracer.trace("foobar", resource=resource, span_type=None) as span: span_id = span.span_id local_root_span_id = span._local_root.span_id - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) ddup.upload() profile = pprof_utils.parse_profile(output_filename) @@ -348,7 +357,7 @@ def target_fun(): time.sleep(1) threads = [] - for _ in range(5): + for _ in range(10): t = threading.Thread(target=target_fun) threads.append(t) t.start() @@ -451,8 +460,8 @@ def sleep_class(cls): return cls().sleep_instance() def sleep_instance(self): - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) test_name = "test_collect_once_with_class" pprof_prefix = str(tmp_path / test_name) @@ -510,8 +519,8 @@ def sleep_class(foobar, cls): return foobar().sleep_instance(cls) def sleep_instance(foobar, self): - for _ in range(5): - time.sleep(0.01) + for _ in range(10): + time.sleep(0.1) test_name = "test_collect_once_with_class" pprof_prefix = str(tmp_path / test_name) @@ -558,3 +567,128 @@ def sleep_instance(foobar, self): ], ), ) + + +def _fib(n): + if n == 1: + return 1 + elif n == 0: + return 0 + else: + return _fib(n - 1) + _fib(n - 2) + + +@pytest.mark.skipif(not TESTING_GEVENT, reason="Not testing gevent") +@pytest.mark.subprocess(ddtrace_run=True) +def test_collect_gevent_thread_task(): + # TODO(taegyunkim): update echion to support gevent and test with stack v2 + + from gevent import monkey + + monkey.patch_all() + + import os + import threading + import time + + from ddtrace.internal.datadog.profiling import ddup + from ddtrace.profiling.collector import stack + from tests.profiling.collector import pprof_utils + from tests.profiling_v2.collector.test_stack import _fib + + test_name = "test_collect_gevent_thread_task" + pprof_prefix = "/tmp/" + test_name + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + # Start some (green)threads + def _dofib(): + for _ in range(5): + # spend some time in CPU so the profiler can catch something + # On a Mac w/ Apple M3 MAX with Python 3.11 it takes about 200ms to calculate _fib(32) + # And _fib() is called 5 times so it should take about 1 second + # We use 5 threads below so it should take about 5 seconds + _fib(32) + # Just make sure gevent switches threads/greenlets + time.sleep(0) + + threads = [] + + with stack.StackCollector(None, _stack_collector_v2_enabled=False): + for i in range(5): + t = threading.Thread(target=_dofib, name="TestThread %d" % i) + t.start() + threads.append(t) + for t in threads: + t.join() + + ddup.upload() + + expected_task_ids = {thread.ident for thread in threads} + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "task id") + assert len(samples) > 0 + + checked_thread = False + + for sample in samples: + task_id_label = pprof_utils.get_label_with_key(profile.string_table, sample, "task id") + task_id = int(task_id_label.num) + if task_id in expected_task_ids: + pprof_utils.assert_stack_event( + profile, + sample, + pprof_utils.StackEvent( + task_name=r"TestThread \d+$", + task_id=task_id, + ), + ) + checked_thread = True + + assert checked_thread, "No samples found for the expected threads" + + +@pytest.mark.parametrize( + ("stack_v2_enabled", "ignore_profiler"), [(True, True), (True, False), (False, True), (False, False)] +) +def test_ignore_profiler(stack_v2_enabled, ignore_profiler, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + test_name = "test_ignore_profiler" + pprof_prefix = str(tmp_path / test_name) + output_filename = pprof_prefix + "." + str(os.getpid()) + + assert ddup.is_available + ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) + ddup.start() + + s = stack.StackCollector(None, ignore_profiler=ignore_profiler, _stack_collector_v2_enabled=stack_v2_enabled) + collector_worker_thread_id = None + + with s: + for _ in range(10): + time.sleep(0.1) + collector_worker_thread_id = s._worker.ident + + ddup.upload() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_label_key(profile, "thread id") + + thread_ids = set() + + for sample in samples: + thread_id_label = pprof_utils.get_label_with_key(profile.string_table, sample, "thread id") + thread_id = int(thread_id_label.num) + thread_ids.add(thread_id) + + # TODO(taegyunkim): update echion to support ignore_profiler and test with stack v2 + if stack_v2_enabled or not ignore_profiler: + assert collector_worker_thread_id in thread_ids + else: + assert collector_worker_thread_id not in thread_ids From 9a4261c9c259e3ce75b86ef8f4c129ed8f2f1dee Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:35:14 -0500 Subject: [PATCH 156/372] chore(ci): add retries for flaky tests (#11269) Co-authored-by: taegyunkim --- .gitlab/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 303333d9cb0..45b01d02f47 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -25,6 +25,8 @@ variables: # Hatch doesn't use pre-built wheels or venvs so we can start them right away needs: [] parallel: 4 + # DEV: This is the max retries that GitLab currently allows for + retry: 2 script: - export PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --ddtrace" - export _DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDER=true @@ -80,6 +82,8 @@ build_base_venvs: parallel: 4 services: - !reference [.services, ddagent] + # DEV: This is the max retries that GitLab currently allows for + retry: 2 script: - pip install riot==0.20.0 - unset DD_SERVICE From 9a8f17112e418c1848551df245b956428ffe127a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20De=20Ara=C3=BAjo?= Date: Thu, 14 Nov 2024 08:09:07 +0000 Subject: [PATCH 157/372] chore(ci_visibility): fix line numbers in Python 3.10 code coverage instrumentation (#11375) The code coverage for Python 3.10 has an implicit assumption that the first bytecode offset matches the first line number in the line table, which is wrong because some instructions may not have line numbers. This PR changes it so that the first line number is the code object's first line number attribute, so that it will work even if no line-starting offsets have been seen before. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../coverage/instrumentation_py3_10.py | 2 +- tests/ci_visibility/test_ci_visibility.py | 6 +-- tests/coverage/included_path/async_code.py | 14 +++++ tests/coverage/test_coverage_async.py | 52 +++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 tests/coverage/included_path/async_code.py create mode 100644 tests/coverage/test_coverage_async.py diff --git a/ddtrace/internal/coverage/instrumentation_py3_10.py b/ddtrace/internal/coverage/instrumentation_py3_10.py index 7561ad24a9b..b328e8beb51 100644 --- a/ddtrace/internal/coverage/instrumentation_py3_10.py +++ b/ddtrace/internal/coverage/instrumentation_py3_10.py @@ -122,7 +122,7 @@ def instrument_all_lines_nonrecursive( previous_line = code.co_firstlineno previous_line_new_offset = 0 - previous_previous_line = 0 + previous_previous_line = code.co_firstlineno arg = 0 previous_arg = 0 diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index b3de47a82b5..ba9c7b5359d 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -528,7 +528,7 @@ def test_git_do_request_agentless(git_repo): {"mock_header_name": "mock_header_value"}, ) - mock_get_connection.assert_called_once_with("http://base_url/repository/endpoint", timeout=20) + mock_get_connection.assert_any_call("http://base_url/repository/endpoint", timeout=20) mock_http_connection.request.assert_called_once_with( "POST", "/repository/endpoint", @@ -561,7 +561,7 @@ def test_git_do_request_evp(git_repo): {"mock_header_name": "mock_header_value"}, ) - mock_get_connection.assert_called_once_with("https://base_url/repository/endpoint", timeout=20) + mock_get_connection.assert_any_call("https://base_url/repository/endpoint", timeout=20) mock_http_connection.request.assert_called_once_with( "POST", "/repository/endpoint", @@ -607,7 +607,7 @@ def test_civisibilitywriter_coverage_agentless_url(self): with mock.patch("ddtrace.internal.writer.writer.get_connection") as _get_connection: _get_connection.return_value.getresponse.return_value.status = 200 dummy_writer._put("", {}, cov_client, no_trace=True) - _get_connection.assert_called_once_with("https://citestcov-intake.datadoghq.com", 2.0) + _get_connection.assert_any_call("https://citestcov-intake.datadoghq.com", 2.0) def test_civisibilitywriter_coverage_agentless_with_intake_url_param(self): ddtrace.internal.ci_visibility.writer.config._ci_visibility_agentless_url = "" diff --git a/tests/coverage/included_path/async_code.py b/tests/coverage/included_path/async_code.py new file mode 100644 index 00000000000..e21d694ff02 --- /dev/null +++ b/tests/coverage/included_path/async_code.py @@ -0,0 +1,14 @@ +import asyncio +import sys + + +async def get_line_number(): + try: + raise Exception("this is line 7") + except Exception: + exception_type, exception, traceback = sys.exc_info() + return traceback.tb_lineno + + +def call_async_function_and_report_line_number(): + return asyncio.run(get_line_number()) diff --git a/tests/coverage/test_coverage_async.py b/tests/coverage/test_coverage_async.py new file mode 100644 index 00000000000..059da55fc7d --- /dev/null +++ b/tests/coverage/test_coverage_async.py @@ -0,0 +1,52 @@ +import pytest + + +@pytest.mark.subprocess +def test_coverage_async_function(): + """ + Async functions in Python 3.10 have an initial GEN_START instruction with no corresponding line number. + This test ensures we can handle this case correctly and build the line number table correctly in the instrumented + functions. + """ + import os + from pathlib import Path + + from ddtrace.internal.coverage.code import ModuleCodeCollector + from ddtrace.internal.coverage.installer import install + from tests.coverage.utils import _get_relpath_dict + + cwd_path = os.getcwd() + include_path = Path(cwd_path + "/tests/coverage/included_path/") + + install(include_paths=[include_path], collect_import_time_coverage=True) + + from tests.coverage.included_path.async_code import call_async_function_and_report_line_number + + ModuleCodeCollector.start_coverage() + line_number = call_async_function_and_report_line_number() + ModuleCodeCollector.stop_coverage() + + executable = _get_relpath_dict(cwd_path, ModuleCodeCollector._instance.lines) + covered = _get_relpath_dict(cwd_path, ModuleCodeCollector._instance._get_covered_lines(include_imported=False)) + covered_with_imports = _get_relpath_dict( + cwd_path, ModuleCodeCollector._instance._get_covered_lines(include_imported=True) + ) + + expected_executable = { + "tests/coverage/included_path/async_code.py": {1, 2, 5, 6, 7, 8, 9, 10, 13, 14}, + } + expected_covered = { + "tests/coverage/included_path/async_code.py": {6, 7, 8, 9, 10, 14}, + } + expected_covered_with_imports = { + "tests/coverage/included_path/async_code.py": {1, 2, 5, 6, 7, 8, 9, 10, 13, 14}, + } + + assert ( + executable == expected_executable + ), f"Executable lines mismatch: expected={expected_executable} vs actual={executable}" + assert covered == expected_covered, f"Covered lines mismatch: expected={expected_covered} vs actual={covered}" + assert ( + covered_with_imports == expected_covered_with_imports + ), f"Covered lines with imports mismatch: expected={expected_covered_with_imports} vs actual={covered_with_imports}" + assert line_number == 7 From a67ad41d1ab62bcfa5b517a78fa97f26627772f7 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:35:52 +0100 Subject: [PATCH 158/372] chore(tracer): remove x-forwarded from ip resolution (#11374) Removed x-forwarded from headers used for client IP resolution (but not from collected headers). We lack evidence of actual usage, and whether this should follow RFC 7239 or regular XFF list format. Also: - add unit test for priority over all ip headers. check that x-forwarded is now ignored. APPSEC-55824 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/trace_utils.py | 2 +- ...d_from_ip_resolution-d63292c0fda42dd9.yaml | 5 ++++ tests/tracer/test_trace_utils.py | 24 ++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/remove_x-forwarded_from_ip_resolution-d63292c0fda42dd9.yaml diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py index b6093e71af0..db8509d8c35 100644 --- a/ddtrace/contrib/trace_utils.py +++ b/ddtrace/contrib/trace_utils.py @@ -1,6 +1,7 @@ """ This module contains utility functions for writing ddtrace integrations. """ + from collections import deque import ipaddress import re @@ -66,7 +67,6 @@ "x-real-ip", "true-client-ip", "x-client-ip", - "x-forwarded", "forwarded-for", "x-cluster-client-ip", "fastly-client-ip", diff --git a/releasenotes/notes/remove_x-forwarded_from_ip_resolution-d63292c0fda42dd9.yaml b/releasenotes/notes/remove_x-forwarded_from_ip_resolution-d63292c0fda42dd9.yaml new file mode 100644 index 00000000000..19bbb487281 --- /dev/null +++ b/releasenotes/notes/remove_x-forwarded_from_ip_resolution-d63292c0fda42dd9.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + Tracer: Removed x-forwarded from headers used for client IP resolution (but not from collected headers). + We lack evidence of actual usage, and whether this should follow RFC 7239 or regular XFF list format. diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index 1fefc505d7f..42cb77fe0b2 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -595,6 +595,27 @@ def test_set_http_meta_case_sensitive_headers_notfound(mock_store_headers, span, mock_store_headers.assert_called() +ALL_IP_HEADERS = ( + ("x-forwarded-for", "1.1.1.1"), + ("x-real-ip", "2.2.2.2"), + ("true-client-ip", "3.3.3.3"), + ("x-client-ip", "4.4.4.4"), + ("x-forwarded", "5.5.5.5"), + ("forwarded-for", "6.6.6.6"), + ("x-cluster-client-ip", "7.7.7.7"), + ("fastly-client-ip", "8.8.8.8"), + ("cf-connecting-ip", "9.9.9.9"), + ("cf-connecting-ipv6", "10.10.10.10"), +) + +# testing priority order +ALL_TESTS = [ + ["", dict(ALL_IP_HEADERS[-1 : -i - 2 : -1]), ALL_IP_HEADERS[-1 - i][1]] for i in range(len(ALL_IP_HEADERS)) +] +# x-forwarded is now ignored so we fall back to forwarded-for +ALL_TESTS[5][2] = "6.6.6.6" + + @pytest.mark.parametrize( "header_env_var,headers_dict,expected", [ @@ -636,7 +657,8 @@ def test_set_http_meta_case_sensitive_headers_notfound(mock_store_headers, span, {"x-forwarded-for": "4.4.4.4", "x-real-ip": "8.8.4.4"}, "8.8.4.4", ), - ], + ] + + ALL_TESTS, ) def test_get_request_header_ip(header_env_var, headers_dict, expected, span): with override_global_config(dict(_asm_enabled=True, _client_ip_header=header_env_var)): From ef58a6b16e63f177c133b94fabb33bd9405630cf Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 14 Nov 2024 14:39:33 +0000 Subject: [PATCH 159/372] chore(er): introduce exception identity (#11335) We introduce the computation of exception identity to limit the number of capturing instances for the same exception over time. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_exception/replay.py | 72 +++++++++++++++++++-- ddtrace/internal/utils/time.py | 6 ++ tests/debugging/exception/test_replay.py | 82 ++++++++++++++++++++++++ tests/tracer/test_utils.py | 5 ++ 4 files changed, 160 insertions(+), 5 deletions(-) diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 15d03c678af..3a54bce6f51 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -20,6 +20,7 @@ from ddtrace.internal.packages import is_user_code from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.internal.utils.time import HourGlass log = get_logger(__name__) @@ -47,13 +48,70 @@ FRAME_FILE_TAG = "_dd.debug.error.%d.file" FRAME_LINE_TAG = "_dd.debug.error.%d.line" +EXCEPTION_IDENT_LIMIT = 3600.0 # 1 hour +EXCEPTION_IDENT_LIMITER: t.Dict[int, HourGlass] = {} + + +ExceptionChain = t.Deque[t.Tuple[BaseException, t.Optional[TracebackType]]] + + +def exception_ident(exc: BaseException, tb: t.Optional[TracebackType]) -> int: + """Compute the identity of an exception. + + We use the exception type and the traceback to generate a unique identifier + that we can use to identify the exception. This can be used to rate limit + the number of times we capture information of the same exception. + """ + h = 0 + _tb = tb + while _tb is not None: + frame = _tb.tb_frame + h = (h << 1) ^ (id(frame.f_code) << 4 | frame.f_lasti) + h &= 0xFFFFFFFFFFFFFFFF + _tb = _tb.tb_next + return (id(type(exc)) << 64) | h + + +def exception_chain_ident(chain: ExceptionChain) -> int: + h = 0 + for exc, tb in chain: + h = (h << 1) ^ exception_ident(exc, tb) + h &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + return h + + +def limit_exception(chain: ExceptionChain) -> bool: + try: + exc_ident = exception_chain_ident(chain) + hg = EXCEPTION_IDENT_LIMITER.get(exc_ident) + if hg is None: + # We haven't seen this exception yet, or it's been evicted + hg = EXCEPTION_IDENT_LIMITER[exc_ident] = HourGlass(duration=EXCEPTION_IDENT_LIMIT) + hg.turn() + return False + + if hg.trickling(): + # We have seen this exception recently + return True + + # We haven't seen this exception in a while + hg.turn() + + return False + finally: + if len(EXCEPTION_IDENT_LIMITER) > 1024: + # We limit the number of exception identities we track to avoid + # memory leaks. + sorted_keys = sorted(EXCEPTION_IDENT_LIMITER, key=lambda k: EXCEPTION_IDENT_LIMITER[k]) + for k in sorted_keys[:256]: + del EXCEPTION_IDENT_LIMITER[k] + def unwind_exception_chain( - exc: t.Optional[BaseException], - tb: t.Optional[TracebackType], -) -> t.Tuple[t.Deque[t.Tuple[BaseException, t.Optional[TracebackType]]], t.Optional[uuid.UUID]]: + exc: t.Optional[BaseException], tb: t.Optional[TracebackType] +) -> t.Tuple[ExceptionChain, t.Optional[uuid.UUID]]: """Unwind the exception chain and assign it an ID.""" - chain: t.Deque[t.Tuple[BaseException, t.Optional[TracebackType]]] = deque() + chain: ExceptionChain = deque() while exc is not None: chain.append((exc, tb)) @@ -160,6 +218,10 @@ def on_span_exception( # No exceptions to capture return + if limit_exception(chain): + # We have seen this exception recently + return + seq = count(1) # 1-based sequence number while chain: @@ -170,7 +232,7 @@ def on_span_exception( continue # DEV: We go from the handler up to the root exception - while _tb and _tb.tb_frame: + while _tb: frame = _tb.tb_frame code = frame.f_code seq_nr = next(seq) diff --git a/ddtrace/internal/utils/time.py b/ddtrace/internal/utils/time.py index b31f3c3e4f1..1993b9dd21c 100644 --- a/ddtrace/internal/utils/time.py +++ b/ddtrace/internal/utils/time.py @@ -134,3 +134,9 @@ def __enter__(self): def __exit__(self, tp, value, traceback): pass + + def __lt__(self, other) -> bool: + return self._end_at < other._end_at + + def __eq__(self, other) -> bool: + return self._end_at == other._end_at diff --git a/tests/debugging/exception/test_replay.py b/tests/debugging/exception/test_replay.py index e2c75ff051e..8b9a2a7d830 100644 --- a/tests/debugging/exception/test_replay.py +++ b/tests/debugging/exception/test_replay.py @@ -1,3 +1,4 @@ +from collections import Counter from contextlib import contextmanager import sys @@ -31,6 +32,46 @@ def test_exception_replay_config_enabled_deprecated(monkeypatch): assert not er_config.enabled +def test_exception_chain_ident(): + def a(v, d=None): + if not v: + raise ValueError("hello", v) + + def b_chain(bar): + m = 4 + try: + a(bar % m) + except ValueError: + raise KeyError("chain it") + + def c(foo=42): + sh = 3 + b_chain(foo << sh) + + exc_idents = Counter() + + for _ in range(100): + try: + c() + except Exception as e: + chain, _ = replay.unwind_exception_chain(e, e.__traceback__) + exc_idents[replay.exception_chain_ident(chain)] += 1 + + assert len(exc_idents) == 1 + + # This generates a different exception chain since c is called at a + # different line number + for _ in range(100): + try: + c() + except Exception as e: + chain, _ = replay.unwind_exception_chain(e, e.__traceback__) + exc_idents[replay.exception_chain_ident(chain)] += 1 + + assert len(exc_idents) == 2 + assert all(v == 100 for v in exc_idents.values()) + + @contextmanager def with_rate_limiter(limiter): original_limiter = replay.GLOBAL_RATE_LIMITER @@ -212,3 +253,44 @@ def b(): assert span_a.name == "a" assert span_a.get_tag(replay.DEBUG_INFO_TAG) == "true" assert span_b.get_tag(replay.DEBUG_INFO_TAG) is None + + def test_debugger_exception_ident_limit(self): + def a(v, d=None): + with self.trace("a"): + if not v: + raise ValueError("hello", v) + + def b_chain(bar): + with self.trace("b"): + m = 4 + try: + a(bar % m) + except ValueError: + raise KeyError("chain it") + + def c(foo=42): + with self.trace("c"): + sh = 3 + b_chain(foo << sh) + + with exception_replay() as uploader: + rate_limiter = RateLimiter( + limit_rate=float("inf"), # no rate limit + raise_on_exceed=False, + ) + with with_rate_limiter(rate_limiter): + with pytest.raises(KeyError): + c() + + self.assert_span_count(3) + assert len(uploader.collector.queue) == 3 + + # Invoke again. The same exception won't be captured again in quick + # succession + with with_rate_limiter(rate_limiter): + with pytest.raises(KeyError): + c() + + self.assert_span_count(6) + # no new snapshots + assert len(uploader.collector.queue) == 3 diff --git a/tests/tracer/test_utils.py b/tests/tracer/test_utils.py index 3603c9105b2..b24396fde7b 100644 --- a/tests/tracer/test_utils.py +++ b/tests/tracer/test_utils.py @@ -589,3 +589,8 @@ def test_hourglass_turn(): while hg.trickling(): sleep(0.1) assert sw.elapsed() > 1.1 + + +def test_hourglass_sorting(): + """Test that we can sort hourglasses.""" + sorted(time.HourGlass(1) for _ in range(100)) From c7246a44c857f1eab1a537ab20ba5f5864b9f280 Mon Sep 17 00:00:00 2001 From: Teague Bick Date: Thu, 14 Nov 2024 11:09:32 -0500 Subject: [PATCH 160/372] =?UTF-8?q?fix(celery):=20handle=20upstream=20cele?= =?UTF-8?q?ry=20patch=20and=20propagation=20during=20an=20e=E2=80=A6=20(#1?= =?UTF-8?q?1272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this change two things weren't working: 1. An upstream Celery patch broke context propagation due to the way ddtrace propagates headers. Prior to this upstream patch, context had to be propagated under kwargs[headers][headers], but after the patch it can be propagated under kwargs[headers]. We now look in the right places for propagated information and retrieve from the right places. 2. Prior to this patch, If Task-A calls Task-B and Task-A errors out before Task-B creates its span, then Task-B would fail to parent itself on Task-A (instead going one level up). This caused errant service-entry `celery.apply` spans on workers, which should always have `celery.run` service entry spans. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/celery/__init__.py | 9 +++++ ddtrace/contrib/internal/celery/constants.py | 3 +- ddtrace/contrib/internal/celery/signals.py | 24 +++++++++--- ddtrace/contrib/internal/celery/utils.py | 38 +++++++++++++++++-- ...-context-propagation-aa904d748362f2d1.yaml | 6 +++ tests/contrib/celery/base.py | 3 +- tests/contrib/celery/test_integration.py | 35 +++++++++++++++++ 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/fix-celery-context-propagation-aa904d748362f2d1.yaml diff --git a/ddtrace/contrib/celery/__init__.py b/ddtrace/contrib/celery/__init__.py index efc851708d9..7b63089e206 100644 --- a/ddtrace/contrib/celery/__init__.py +++ b/ddtrace/contrib/celery/__init__.py @@ -24,6 +24,15 @@ def run(self): .. py:data:: ddtrace.config.celery['distributed_tracing'] Whether or not to pass distributed tracing headers to Celery workers. + Note: this flag applies to both Celery workers and callers separately. + + On the caller: enabling propagation causes the caller and worker to + share a single trace while disabling causes them to be separate. + + On the worker: enabling propagation causes context to propagate across + tasks, such as when Task A queues work for Task B, or if Task A retries. + Disabling propagation causes each celery.run task to be in its own + separate trace. Can also be enabled with the ``DD_CELERY_DISTRIBUTED_TRACING`` environment variable. diff --git a/ddtrace/contrib/internal/celery/constants.py b/ddtrace/contrib/internal/celery/constants.py index 39923a08bc7..3bf25235e88 100644 --- a/ddtrace/contrib/internal/celery/constants.py +++ b/ddtrace/contrib/internal/celery/constants.py @@ -2,7 +2,8 @@ # Celery Context key -CTX_KEY = "__dd_task_span" +SPAN_KEY = "__dd_task_span" +CTX_KEY = "__dd_task_context" # Span names PRODUCER_ROOT_SPAN = "celery.apply" diff --git a/ddtrace/contrib/internal/celery/signals.py b/ddtrace/contrib/internal/celery/signals.py index 6341bed9bbf..e235d6efb3b 100644 --- a/ddtrace/contrib/internal/celery/signals.py +++ b/ddtrace/contrib/internal/celery/signals.py @@ -9,8 +9,10 @@ from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.celery import constants as c from ddtrace.contrib.internal.celery.utils import attach_span +from ddtrace.contrib.internal.celery.utils import attach_span_context from ddtrace.contrib.internal.celery.utils import detach_span from ddtrace.contrib.internal.celery.utils import retrieve_span +from ddtrace.contrib.internal.celery.utils import retrieve_span_context from ddtrace.contrib.internal.celery.utils import retrieve_task_id from ddtrace.contrib.internal.celery.utils import set_tags_from_context from ddtrace.ext import SpanKind @@ -43,6 +45,7 @@ def trace_prerun(*args, **kwargs): return request_headers = task.request.get("headers", {}) + request_headers = request_headers or retrieve_span_context(task, task_id) trace_utils.activate_distributed_headers(pin.tracer, int_config=config.celery, request_headers=request_headers) # propagate the `Span` in the current task Context @@ -65,6 +68,8 @@ def trace_prerun(*args, **kwargs): span.set_tag(SPAN_MEASURED_KEY) attach_span(task, task_id, span) + if config.celery["distributed_tracing"]: + attach_span_context(task, task_id, span) def trace_postrun(*args, **kwargs): @@ -110,6 +115,13 @@ def trace_before_publish(*args, **kwargs): if pin is None: return + # If Task A calls Task B, and Task A excepts, then Task B may have no parent when apply is called. + # In these cases, we don't use the "current context" of attached span/tracer, for context, we use + # the attached distributed context. + if config.celery["distributed_tracing"]: + request_headers = retrieve_span_context(task, task_id, is_publish=False) + trace_utils.activate_distributed_headers(pin.tracer, int_config=config.celery, request_headers=request_headers) + # apply some tags here because most of the data is not available # in the task_after_publish signal service = config.celery["producer_service_name"] @@ -145,11 +157,13 @@ def trace_before_publish(*args, **kwargs): trace_headers = {} propagator.inject(span.context, trace_headers) - # put distributed trace headers where celery will propagate them - task_headers = kwargs.get("headers") or {} - task_headers.setdefault("headers", {}) - task_headers["headers"].update(trace_headers) - kwargs["headers"] = task_headers + kwargs.setdefault("headers", {}) + + # This is a hack for other versions, such as https://github.com/celery/celery/issues/4875 + # We always uses the double ["headers"]["headers"] because it works both before and + # after the changes made in celery + kwargs["headers"].setdefault("headers", {}) + kwargs["headers"]["headers"].update(trace_headers) def trace_after_publish(*args, **kwargs): diff --git a/ddtrace/contrib/internal/celery/utils.py b/ddtrace/contrib/internal/celery/utils.py index e18f83ba0dc..8945fb5857b 100644 --- a/ddtrace/contrib/internal/celery/utils.py +++ b/ddtrace/contrib/internal/celery/utils.py @@ -4,8 +4,13 @@ from ddtrace._trace.span import Span from ddtrace.contrib.trace_utils import set_flattened_tags +from ddtrace.propagation.http import HTTPPropagator from .constants import CTX_KEY +from .constants import SPAN_KEY + + +propagator = HTTPPropagator TAG_KEYS = frozenset( @@ -66,6 +71,31 @@ def set_tags_from_context(span: Span, context: Dict[str, Any]) -> None: set_flattened_tags(span, context_tags) +def attach_span_context(task, task_id, span, is_publish=False): + trace_headers = {} + propagator.inject(span.context, trace_headers) + + # put distributed trace headers where celery will propagate them + context_dict = getattr(task, CTX_KEY, None) + if context_dict is None: + context_dict = dict() + setattr(task, CTX_KEY, context_dict) + + context_dict[(task_id, is_publish, "distributed_context")] = trace_headers + + +def retrieve_span_context(task, task_id, is_publish=False): + """Helper to retrieve an active `Span` stored in a `Task` + instance + """ + context_dict = getattr(task, CTX_KEY, None) + if context_dict is None: + return + + # DEV: See note in `attach_span` for key info + return context_dict.get((task_id, is_publish, "distributed_context")) + + def attach_span(task, task_id, span, is_publish=False): """Helper to propagate a `Span` for the given `Task` instance. This function uses a `WeakValueDictionary` that stores a Datadog Span using @@ -85,10 +115,10 @@ def attach_span(task, task_id, span, is_publish=False): NOTE: We cannot test for this well yet, because we do not run a celery worker, and cannot run `task.apply_async()` """ - weak_dict = getattr(task, CTX_KEY, None) + weak_dict = getattr(task, SPAN_KEY, None) if weak_dict is None: weak_dict = WeakValueDictionary() - setattr(task, CTX_KEY, weak_dict) + setattr(task, SPAN_KEY, weak_dict) weak_dict[(task_id, is_publish)] = span @@ -97,7 +127,7 @@ def detach_span(task, task_id, is_publish=False): """Helper to remove a `Span` in a Celery task when it's propagated. This function handles tasks where the `Span` is not attached. """ - weak_dict = getattr(task, CTX_KEY, None) + weak_dict = getattr(task, SPAN_KEY, None) if weak_dict is None: return @@ -112,7 +142,7 @@ def retrieve_span(task, task_id, is_publish=False): """Helper to retrieve an active `Span` stored in a `Task` instance """ - weak_dict = getattr(task, CTX_KEY, None) + weak_dict = getattr(task, SPAN_KEY, None) if weak_dict is None: return else: diff --git a/releasenotes/notes/fix-celery-context-propagation-aa904d748362f2d1.yaml b/releasenotes/notes/fix-celery-context-propagation-aa904d748362f2d1.yaml new file mode 100644 index 00000000000..39c7ae98ce9 --- /dev/null +++ b/releasenotes/notes/fix-celery-context-propagation-aa904d748362f2d1.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + celery: This fix resolves two issues with context propagation in celery + 1. Invalid span parentage when task A calls task B async and task A errors out, causing A's queuing of B, and B itself to not be parented under A. + 2. Invalid context propagation from client to workers, and across retries, causing multiple traces instead of a single trace \ No newline at end of file diff --git a/tests/contrib/celery/base.py b/tests/contrib/celery/base.py index 65c1348ac49..46055bc6070 100644 --- a/tests/contrib/celery/base.py +++ b/tests/contrib/celery/base.py @@ -18,7 +18,7 @@ @pytest.fixture(scope="session") def celery_config(): - return {"broker_url": BROKER_URL, "result_backend": BACKEND_URL} + return {"broker_url": BROKER_URL, "result_backend": BACKEND_URL, "task_concurrency": 5} @pytest.fixture @@ -26,6 +26,7 @@ def celery_worker_parameters(): return { # See https://github.com/celery/celery/issues/3642#issuecomment-457773294 "perform_ping_check": False, + "concurrency": 2, } diff --git a/tests/contrib/celery/test_integration.py b/tests/contrib/celery/test_integration.py index 5486587311c..03276a79296 100644 --- a/tests/contrib/celery/test_integration.py +++ b/tests/contrib/celery/test_integration.py @@ -442,6 +442,41 @@ def run(self): assert span.get_tag("span.kind") == "consumer" assert span.error == 0 + def test_task_chain_same_trace(self): + @self.app.task(max_retries=1, default_retry_delay=1) + def fn_b(user, force_logout=False): + raise ValueError("Foo") + + self.celery_worker.reload() # Reload after each task or we get an unregistered error + + @self.app.task(bind=True, max_retries=1, autoretry_for=(Exception,), default_retry_delay=1) + def fn_a(self, user, force_logout=False): + fn_b.apply_async(args=[user], kwargs={"force_logout": force_logout}) + raise ValueError("foo") + + self.celery_worker.reload() # Reload after each task or we get an unregistered error + + traces = None + try: + with self.override_config("celery", dict(distributed_tracing=True)): + t = fn_a.apply_async(args=["user"], kwargs={"force_logout": True}) + # We wait 10 seconds so all tasks finish. While it'd be nice to block + # until all tasks complete, celery doesn't offer an option. Using get() + # causes a deadlock, since in test-mode we only have one worker. + import time + + time.sleep(10) + t.get() + except Exception: + pass + + traces = self.pop_traces() + # The below tests we have 1 trace with 8 spans, which is the shape generated + assert len(traces) > 0 + assert sum([1 for trace in traces for span in trace]) == 8 + trace_id = traces[0][0].trace_id + assert all(trace_id == span.trace_id for trace in traces for span in trace) + @mock.patch("kombu.messaging.Producer.publish", mock.Mock(side_effect=ValueError)) def test_fn_task_apply_async_soft_exception(self): # If the underlying library runs into an exception that doesn't crash the app From c60e52d06d9e520e49a45a57a843e9229230cb9a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 14 Nov 2024 13:19:33 -0500 Subject: [PATCH 161/372] ci(asm): appsec_integrations suite improvments (#11411) 1. Increase parallelism to reduce the time to run the suite, there are 13 riot hashes. Each job takes about 30 minutes, where 10 minutes spent for building dd-trace-py and the rest spent for running 2 riot hashes. 3. Mark a test flaky, it failed 3 times in a row for an unrelated change [ci(profiling): explicitly set cpucount for uwsgi build](https://github.com/DataDog/dd-trace-py/pull/11402), [run 1](https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/77339/workflows/b138b8df-bdb5-4cfc-a6ae-e8d8f204661b/jobs/4357608), [run 2](https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/77339/workflows/8e33c607-89cb-4bab-89b1-acb6d0931856/jobs/4357577), [run 3](https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/77339/workflows/b5609072-9b15-4c46-ae11-10c7c0039ada/jobs/4357516). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 2 +- .../appsec/integrations/test_flask_entrypoint_iast_patches.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 7beaabf9791..7e87a09868d 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -521,7 +521,7 @@ jobs: appsec_integrations: <<: *machine_executor - parallelism: 7 + parallelism: 13 steps: - run_test: pattern: 'appsec_integrations' diff --git a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py index 6a10f2b53f4..f0eeb1eb626 100644 --- a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py +++ b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py @@ -1,5 +1,7 @@ import pytest +from tests.utils import flaky + @pytest.mark.subprocess() def test_ddtrace_iast_flask_patch(): @@ -146,6 +148,7 @@ def _uninstall_watchdog_and_reload(): del sys.modules["tests.appsec.iast.fixtures.entrypoint.views"] +@flaky(1736035200) @pytest.mark.subprocess(check_logs=False) def test_ddtrace_iast_flask_app_create_app_patch_all_enable_iast_propagation(): import dis From ba08a9898c9641c909f5272ca86945ae872aa395 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 14 Nov 2024 14:24:03 -0500 Subject: [PATCH 162/372] ci(profiling): set timeout for gunicorn test (#11403) https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/706502737 gunicorn test times out frequently as above link. Set timeouts for - `urllib.request.urlopen()` - `proc.wait()` Also, update the port to a different one from profile (v1) test. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling_v2/test_gunicorn.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index ef97d9683f8..cf798174d9b 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -30,7 +30,7 @@ def debug_print(*args): def _run_gunicorn(*args): cmd = ( - ["ddtrace-run", "gunicorn", "--bind", "127.0.0.1:7643", "--chdir", os.path.dirname(__file__)] + ["ddtrace-run", "gunicorn", "--bind", "127.0.0.1:7644", "--chdir", os.path.dirname(__file__)] + list(args) + ["tests.profiling.gunicorn-app:app"] ) @@ -58,32 +58,43 @@ def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): filename = str(tmp_path / "gunicorn.pprof") monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) + debug_print("Creating gunicorn workers") # DEV: We only start 1 worker to simplify the test proc = gunicorn("-w", "1", *args) # Wait for the workers to start - time.sleep(3) + time.sleep(5) + if proc.poll() is not None: + pytest.fail("Gunicorn failed to start") + + debug_print("Making request to gunicorn server") try: - with urllib.request.urlopen("http://127.0.0.1:7643") as f: + with urllib.request.urlopen("http://127.0.0.1:7644", timeout=5) as f: status_code = f.getcode() assert status_code == 200, status_code response = f.read().decode() debug_print(response) - except Exception as e: pytest.fail("Failed to make request to gunicorn server %s" % e) finally: # Need to terminate the process to get the output and release the port proc.terminate() + debug_print("Reading gunicorn worker output to get PIDs") output = proc.stdout.read().decode() worker_pids = _get_worker_pids(output) + debug_print("Gunicorn worker PIDs: %s" % worker_pids) for line in output.splitlines(): debug_print(line) assert len(worker_pids) == 1, output - assert proc.wait() == 0, output + + debug_print("Waiting for gunicorn process to terminate") + try: + assert proc.wait(timeout=5) == 0, output + except subprocess.TimeoutExpired: + pytest.fail("Failed to terminate gunicorn process ", output) assert "module 'threading' has no attribute '_active'" not in output, output for pid in worker_pids: From 68ce56bff2de953063332a20ec60dd1d625845c9 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 14 Nov 2024 15:04:59 -0500 Subject: [PATCH 163/372] ci(profiling): explicitly set cpucount for uwsgi build (#11402) As in https://uwsgi-docs.readthedocs.io/en/latest/BuildSystem.html#cpucount When uwsgi is built, it tries to detect the number of cores available automatically, and it detects absurdly high number of cpus, 64 here https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/706525120 Set the number to be 12 as we do for CMAKE_BUILD_PARALLEL_LEVEL This is to reduce uwsgi build failures leading to test failures ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- riotfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/riotfile.py b/riotfile.py index 4de6347f85a..bcd6629c783 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2842,6 +2842,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): env={ "DD_PROFILING_ENABLE_ASSERTS": "1", "DD_PROFILING__FORCE_LEGACY_EXPORTER": "1", + "CPUCOUNT": "12", }, pkgs={ "gunicorn": latest, @@ -2979,6 +2980,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "DD_PROFILING_EXPORT_LIBDD_ENABLED": "1", # Enable pytest v2 plugin to handle pytest-cpp items in the test suite "_DD_CIVISIBILITY_USE_PYTEST_V2": "1", + "CPUCOUNT": "12", }, pkgs={ "gunicorn": latest, From 691604985643cf6ca74fa194f9b185b7e3ebd762 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:16:49 -0500 Subject: [PATCH 164/372] chore(ci): add retry to the run test step for circleci (#11368) Co-authored-by: taegyunkim --- .circleci/config.templ.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 7e87a09868d..db016870e75 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -188,6 +188,7 @@ commands: RIOT_RUN_RECOMPILE_REQS: "<< pipeline.parameters.riot_run_latest >>" DD_CIVISIBILITY_AGENTLESS_ENABLED: true no_output_timeout: 5m + attempts: 2 command: | ulimit -c unlimited ./scripts/run-test-suite '<>' <> 1 From bd0097f2ee90953781b3830507a7890be84a1119 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:51:27 -0500 Subject: [PATCH 165/372] chore(ci): check if target branch is too old (#11246) Co-authored-by: taegyunkim --- .github/workflows/check_old_target_branch.yml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/check_old_target_branch.yml diff --git a/.github/workflows/check_old_target_branch.yml b/.github/workflows/check_old_target_branch.yml new file mode 100644 index 00000000000..73925f75290 --- /dev/null +++ b/.github/workflows/check_old_target_branch.yml @@ -0,0 +1,36 @@ +name: Check for Old Target Branch + +on: + pull_request: + +jobs: + check_target_branch: + name: "Check for old target branch" + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Check if target branch is too old to backport + id: check-branch + run: | + # Define regex for branches with major version 0 or 1, or versions from 2.0 to 2.12 + old_branch_regex="^(0|1)(\\.|$)|^2\\.([0-9]|1[0-2])(\\.|$)" + target_branch="${{ github.event.pull_request.base.ref }}" + + if [[ "$target_branch" =~ $old_branch_regex ]]; then + echo "Old target branch detected: $target_branch" + echo "old_branch=true" >> $GITHUB_ENV + else + echo "old_branch=false" >> $GITHUB_ENV + fi + + - name: Old branch warning on PR + if: env.old_branch == 'true' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + 🚫 **This target branch is too old or unsupported. Please update the target branch to continue.** + + - name: Fail the job if branch is old + if: env.old_branch == 'true' + run: exit 1 From 19f6a8cabc0662ce51deb97921a801933e9999f6 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Thu, 14 Nov 2024 20:03:15 -0500 Subject: [PATCH 166/372] chore(ci): update description of generate package workflow (#11352) In the auto-generated PRs generated by the [Generate Package Versions](https://github.com/DataDog/dd-trace-py/actions/workflows/generate-package-versions.yml) workflow, some of the modified files might not directly update the selected package to `latest`. Not all lockfiles for a given `venv` will use the `venv` `latest`, but the workflow also updates dependency versions. This PR modifies the body of the generated PRs for better clarification. Example: #11354 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .../workflows/generate-package-versions.yml | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index b375ec95864..e674c039b4c 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -99,4 +99,30 @@ jobs: base: main title: "chore: update ${{ env.VENV_NAME }} latest version to ${{ env.NEW_LATEST }}" labels: changelog/no-changelog - body-path: .github/PULL_REQUEST_TEMPLATE.md + body: | + Update ${{ env.VENV_NAME }} lockfiles and dependency package lockfiles. + This performs the following updates: + 1) Some ${{ env.VENV_NAME }} lockfiles use ${{ env.VENV_NAME }} `latest`. This will update ${{ env.VENV_NAME }} and dependencies. + 2) Some ${{ env.VENV_NAME }} lockfiles use a pinned (non-latest) version of ${{ env.VENV_NAME }}, but require the `latest` version of another package. This will update all such packages. + + ## Checklist + - [x] PR author has checked that all the criteria below are met + - The PR description includes an overview of the change + - The PR description articulates the motivation for the change + - The change includes tests OR the PR description describes a testing strategy + - The PR description notes risks associated with the change, if any + - Newly-added code is easy to change + - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) + - The change includes or references documentation updates if necessary + - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) + + ## Reviewer Checklist + - [ ] Reviewer has checked that all the criteria below are met + - Title is accurate + - All changes are related to the pull request's stated goal + - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes + - Testing strategy adequately addresses listed risks + - Newly-added code is easy to change + - Release note makes sense to a user of the library + - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment + - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) From 6c628db9ab1d34919bec5a6e3ba12e0c73e24ac4 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:52:45 +0000 Subject: [PATCH 167/372] ci: switch repo to use pytest plugin v2 (#11379) This makes tweaks to run the new version of the `pytest` plugin on the `dd-trace-py` repostitory. - the coverage collector is tweaked to only cover use code, and always ignore the coverage module itself - ITR is disabled for the `appsec` suite - `_CI_DD_ENV` is introduced as a way to override the `DD_ENV` from the environment (as setting it in our current CI breaks dozens of unrelated tests) - the `-s` flag is added to `pytest` invocations for output to appear - a test is refactored to account for the fact that `ModuleWatchdog` now has children classes that may be active ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/tests.yml | 3 ++- ddtrace/contrib/pytest/_plugin_v2.py | 2 +- ddtrace/internal/ci_visibility/recorder.py | 8 +++++--- ddtrace/internal/ci_visibility/writer.py | 2 +- ddtrace/internal/coverage/code.py | 7 +++++++ riotfile.py | 6 +++++- tests/internal/test_module.py | 5 +++-- 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 45b01d02f47..cc0575ce396 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -4,9 +4,10 @@ stages: variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s REPO_LANG: python # "python" is used everywhere rather than "py" + _DD_CIVISIBILITY_USE_PYTEST_V2: "true" + PYTEST_ADDOPTS: "-s" # CI_DEBUG_SERVICES: "true" - .testrunner: image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:7a2e802af76051f82d698919d2837eff18dbb48e # DEV: we have a larger pool of amd64 runners, prefer that over arm64 diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 7dc7d278de7..c8d94ab0b3f 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -160,6 +160,7 @@ def pytest_load_initial_conftests(early_config, parser, args): try: take_over_logger_stream_handler() + log.warning("This version of the ddtrace pytest plugin is currently in beta.") dd_config.test_visibility.itr_skipping_level = ITR_SKIPPING_LEVEL.SUITE enable_test_visibility(config=dd_config.pytest) if InternalTestSession.should_collect_coverage(): @@ -176,7 +177,6 @@ def pytest_load_initial_conftests(early_config, parser, args): def pytest_configure(config: pytest_Config) -> None: try: if is_enabled(config): - take_over_logger_stream_handler() unpatch_unittest() enable_test_visibility(config=dd_config.pytest) if _is_pytest_cov_enabled(config): diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 42d37c34927..8d1922b8663 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -221,6 +221,8 @@ def __init__(self, tracer=None, config=None, service=None): self._git_data: GitData = get_git_data_from_tags(self._tags) + dd_env = os.getenv("_CI_DD_ENV", ddconfig.env) + if ddconfig._ci_visibility_agentless_enabled: if not self._api_key: raise EnvironmentError( @@ -237,7 +239,7 @@ def __init__(self, tracer=None, config=None, service=None): self._dd_site, ddconfig._ci_visibility_agentless_url if ddconfig._ci_visibility_agentless_url else None, self._service, - ddconfig.env, + dd_env, ) elif self._agent_evp_proxy_is_available(): self._requests_mode = REQUESTS_MODE.EVP_PROXY_EVENTS @@ -248,7 +250,7 @@ def __init__(self, tracer=None, config=None, service=None): self._configurations, self.tracer._agent_url, self._service, - ddconfig.env, + dd_env, ) else: requests_mode_str = "APM (some features will be disabled)" @@ -267,7 +269,7 @@ def __init__(self, tracer=None, config=None, service=None): self._configure_writer(coverage_enabled=self._collect_coverage_enabled, url=self.tracer._agent_url) - log.info("Service: %s (env: %s)", self._service, ddconfig.env) + log.info("Service: %s (env: %s)", self._service, dd_env) log.info("Requests mode: %s", requests_mode_str) log.info("Git metadata upload enabled: %s", self._should_upload_git_metadata) log.info("API-provided settings: coverage collection: %s", self._api_settings.coverage_enabled) diff --git a/ddtrace/internal/ci_visibility/writer.py b/ddtrace/internal/ci_visibility/writer.py index 45b801fca93..702eb875426 100644 --- a/ddtrace/internal/ci_visibility/writer.py +++ b/ddtrace/internal/ci_visibility/writer.py @@ -51,7 +51,7 @@ def __init__(self): "*", { "language": "python", - "env": config.env, + "env": os.getenv("_CI_DD_ENV", config.env), "runtime-id": get_runtime_id(), "library_version": ddtrace.__version__, }, diff --git a/ddtrace/internal/coverage/code.py b/ddtrace/internal/coverage/code.py index 5ae23742d9d..b6f5d379661 100644 --- a/ddtrace/internal/coverage/code.py +++ b/ddtrace/internal/coverage/code.py @@ -13,6 +13,7 @@ from ddtrace.internal.coverage.util import collapse_ranges from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog +from ddtrace.internal.packages import is_user_code from ddtrace.internal.packages import platlib_path from ddtrace.internal.packages import platstdlib_path from ddtrace.internal.packages import purelib_path @@ -46,6 +47,9 @@ def __init__(self) -> None: # in the root directory of a repository) self._exclude_paths: t.List[Path] = [stdlib_path, platstdlib_path, platlib_path, purelib_path] + # Avoid instrumenting anything in the current module + self._exclude_paths.append(Path(__file__).resolve().parent) + self._coverage_enabled: bool = False self.seen: t.Set[t.Tuple[CodeType, str]] = set() @@ -323,6 +327,9 @@ def transform(self, code: CodeType, _module: ModuleType) -> CodeType: # Don't instrument code from standard library/site packages/etc. return code + if not is_user_code(code_path): + return code + retval = self.instrument_code(code, _module.__package__ if _module is not None else "") if self._collect_import_coverage: diff --git a/riotfile.py b/riotfile.py index bcd6629c783..6c9f13fc62f 100644 --- a/riotfile.py +++ b/riotfile.py @@ -105,6 +105,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "DD_INJECT_FORCE": "1", "DD_PATCH_MODULES": "unittest:false", "CMAKE_BUILD_PARALLEL_LEVEL": "12", + "_DD_CIVISIBILITY_USE_PYTEST_V2": "true", }, venvs=[ Venv( @@ -136,6 +137,9 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "requests": latest, "docker": latest, }, + env={ + "DD_CIVISIBILITY_ITR_ENABLED": "0", + }, ), Venv( name="appsec_iast", @@ -1720,7 +1724,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): ), Venv( name="pytest-benchmark", - command="pytest {cmdargs} --no-cov tests/contrib/pytest_benchmark/", + command="pytest {cmdargs} --no-ddtrace --no-cov tests/contrib/pytest_benchmark/", pkgs={ "msgpack": latest, "pytest-randomly": latest, diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index 2d27899ef3b..b3cacdc9e17 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -7,6 +7,7 @@ import mock import pytest +from ddtrace.internal.coverage.code import ModuleCodeCollector from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.module import origin import tests.test_module @@ -52,7 +53,7 @@ def module_watchdog(): def test_watchdog_install_uninstall(): assert not ModuleWatchdog.is_installed() - assert not any(isinstance(m, ModuleWatchdog) for m in sys.meta_path) + assert not any(isinstance(m, ModuleWatchdog) and not isinstance(m, ModuleCodeCollector) for m in sys.meta_path) ModuleWatchdog.install() @@ -62,7 +63,7 @@ def test_watchdog_install_uninstall(): ModuleWatchdog.uninstall() assert not ModuleWatchdog.is_installed() - assert not any(isinstance(m, ModuleWatchdog) for m in sys.meta_path) + assert not any(isinstance(m, ModuleWatchdog) and not isinstance(m, ModuleCodeCollector) for m in sys.meta_path) def test_import_origin_hook_for_imported_module(module_watchdog): From 948b0cae68220f430da98ddf1dcd8c6fce5a0bc3 Mon Sep 17 00:00:00 2001 From: wantsui Date: Fri, 15 Nov 2024 04:01:04 -0500 Subject: [PATCH 168/372] chore: extend flaky `until` timestamp for grpc (#11399) As part of https://github.com/DataDog/dd-trace-py/pull/11274, the previous `until` timestamp for grpc was Oct 2024 which has already passed. Unless anyone knows about ipv6 support, this PR extends the timestamp until Jan 2025 to buy some more time. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/grpc/test_grpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/grpc/test_grpc.py b/tests/contrib/grpc/test_grpc.py index ebe955cdefd..fdde555c83d 100644 --- a/tests/contrib/grpc/test_grpc.py +++ b/tests/contrib/grpc/test_grpc.py @@ -669,7 +669,7 @@ def service(self, handler_call_details): @snapshot(ignores=["meta.network.destination.port"], wait_for_num_traces=2) -@flaky(until=1729294610, reason="GitLab CI does not support ipv6 at this time") +@flaky(until=1738272799, reason="GitLab CI does not support ipv6 at this time") def test_method_service(patch_grpc): def handler(request, context): return b"" From 1aaed62879e50a9ddee8438fa04ee8ceb1d2950d Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Fri, 15 Nov 2024 08:42:09 -0500 Subject: [PATCH 169/372] chore: update changelog for version 2.16.2 (#11410) - [x] update changelog for version 2.16.2 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b9e1a0bc1..9414d839d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.16.2 + + +### Bug Fixes + +- Profiling + - The lock profiler would log a warning if it couldn't determine a name for a lock, and it would try determining a name multiple times for the same lock. This lead to excessive log spam. Downgrade this to a debug log and only try to determine the name once. + +- Tracing + - pymongo: Adds type checking to solve an issue where `NoneType` instead of expected `Pin` object would throw an error in `TracedTopology` method. + + --- ## 2.14.6 From ecd0ab1813c218691d55c709730970686058469c Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Fri, 15 Nov 2024 13:24:11 -0500 Subject: [PATCH 170/372] fix(botocore): [SVLS-5973] less noisy span pointers (#11353) Span pointer issues should be debug messages rather than warnings since they are not really actionable from the perspective of our users. We'd also like to have some instrumentation telemetry to see how the logic is doing. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/_span_pointer.py | 27 +- ddtrace/_trace/telemetry.py | 52 ++ .../utils_botocore/span_pointers/__init__.py | 30 +- .../utils_botocore/span_pointers/dynamodb.py | 465 +++++++++++++----- .../_trace/utils_botocore/span_pointers/s3.py | 86 ++-- .../utils_botocore/span_pointers/telemetry.py | 18 + ...e-span-pointer-noise-e1cf01ec581a409c.yaml | 4 + tests/tracer/test_telemetry.py | 67 +++ .../utils_botocore/test_span_pointers.py | 183 ++++--- 9 files changed, 706 insertions(+), 226 deletions(-) create mode 100644 ddtrace/_trace/telemetry.py create mode 100644 ddtrace/_trace/utils_botocore/span_pointers/telemetry.py create mode 100644 releasenotes/notes/reduce-span-pointer-noise-e1cf01ec581a409c.yaml create mode 100644 tests/tracer/test_telemetry.py diff --git a/ddtrace/_trace/_span_pointer.py b/ddtrace/_trace/_span_pointer.py index 9b2c661047e..015aac7fc12 100644 --- a/ddtrace/_trace/_span_pointer.py +++ b/ddtrace/_trace/_span_pointer.py @@ -8,6 +8,7 @@ from ddtrace._trace._span_link import SpanLink from ddtrace._trace._span_link import SpanLinkKind +from ddtrace._trace.telemetry import record_span_pointer_calculation_issue from ddtrace.internal.logger import get_logger @@ -67,20 +68,28 @@ def __post_init__(self): def _standard_hashing_function(*elements: bytes) -> str: try: if not elements: - raise ValueError("elements must not be empty") + return _standard_hashing_function_failure("elements must not be empty") # Please see the tests for more details about this logic. return sha256(b"|".join(elements)).hexdigest()[:32] except Exception as e: - log.warning( - "failed to generate standard hash for span pointer: %s", - str(e), - ) - return _add_random_suffix( - prefix=_STANDARD_HASHING_FUNCTION_FAILURE_PREFIX, - minimum_length=32, - ) + return _standard_hashing_function_failure(str(e)) + + +def _standard_hashing_function_failure(reason: str) -> str: + log.debug( + "failed to generate standard hash for span pointer: %s", + reason, + ) + record_span_pointer_calculation_issue( + context="standard_hashing_function", + ) + + return _add_random_suffix( + prefix=_STANDARD_HASHING_FUNCTION_FAILURE_PREFIX, + minimum_length=32, + ) def _add_random_suffix(*, prefix: str, minimum_length: int) -> str: diff --git a/ddtrace/_trace/telemetry.py b/ddtrace/_trace/telemetry.py new file mode 100644 index 00000000000..4611cedba51 --- /dev/null +++ b/ddtrace/_trace/telemetry.py @@ -0,0 +1,52 @@ +from typing import Optional +from typing import Tuple + +from ddtrace.internal.telemetry import telemetry_writer + + +def record_span_pointer_calculation(context: str, span_pointer_count: int) -> None: + telemetry_writer.add_count_metric( + namespace="tracer", + name="span_pointer_calculation", + value=1, + tags=(("context", context), ("count", _span_pointer_count_to_tag(span_pointer_count))), + ) + + +def _span_pointer_count_to_tag(span_pointer_count: int) -> str: + if span_pointer_count < 0: + # this shouldn't be possible, but let's make sure + return "negative" + + elif span_pointer_count <= 5: + return str(span_pointer_count) + + elif span_pointer_count <= 10: + return "6-10" + + elif span_pointer_count <= 20: + return "11-20" + + elif span_pointer_count <= 50: + return "21-50" + + elif span_pointer_count <= 100: + return "51-100" + + else: + return "101+" + + +def record_span_pointer_calculation_issue( + context: str, additional_tags: Optional[Tuple[Tuple[str, str], ...]] = None +) -> None: + tags: Tuple[Tuple[str, str], ...] = (("context", context),) + if additional_tags: + tags += additional_tags + + telemetry_writer.add_count_metric( + namespace="tracer", + name="span_pointer_calculation.issue", + value=1, + tags=tags, + ) diff --git a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py index c9765b30cfb..d4d724c38cb 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py @@ -14,6 +14,12 @@ # import from here as well. from ddtrace._trace.utils_botocore.span_pointers.s3 import _aws_s3_object_span_pointer_description # noqa: F401 from ddtrace._trace.utils_botocore.span_pointers.s3 import _extract_span_pointers_for_s3_response +from ddtrace._trace.utils_botocore.span_pointers.telemetry import record_span_pointer_calculation +from ddtrace._trace.utils_botocore.span_pointers.telemetry import record_span_pointer_calculation_issue +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) def extract_span_pointers_from_successful_botocore_response( @@ -23,12 +29,22 @@ def extract_span_pointers_from_successful_botocore_response( request_parameters: Dict[str, Any], response: Dict[str, Any], ) -> List[_SpanPointerDescription]: - if endpoint_name == "s3": - return _extract_span_pointers_for_s3_response(operation_name, request_parameters, response) + result = [] + + try: + if endpoint_name == "s3": + result = _extract_span_pointers_for_s3_response(operation_name, request_parameters, response) + + elif endpoint_name == "dynamodb": + result = _extract_span_pointers_for_dynamodb_response( + dynamodb_primary_key_names_for_tables, operation_name, request_parameters, response + ) + + except Exception as e: + # Catch-all in case we miss something in the helpers + log.debug("Error extracting span pointers from botocore response: %s", e) + record_span_pointer_calculation_issue("extractor_root", "unexpected_error") - if endpoint_name == "dynamodb": - return _extract_span_pointers_for_dynamodb_response( - dynamodb_primary_key_names_for_tables, operation_name, request_parameters, response - ) + record_span_pointer_calculation(span_pointer_count=len(result)) - return [] + return result diff --git a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py index 3b7d99cb0e4..89cc8c6f6e8 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py @@ -1,9 +1,11 @@ from copy import deepcopy +from enum import Enum import itertools import sys from typing import Any from typing import Dict from typing import List +from typing import Optional from typing import Set from typing import Union from typing import cast @@ -11,6 +13,7 @@ from ddtrace._trace._span_pointer import _SpanPointerDescription from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace._span_pointer import _standard_hashing_function +from ddtrace._trace.utils_botocore.span_pointers.telemetry import record_span_pointer_calculation_issue from ddtrace.internal.logger import get_logger @@ -22,6 +25,14 @@ log = get_logger(__name__) +class _TelemetryIssueTags(Enum): + REQUEST_PARAMETERS = "request_parameters" + HASHING_FAILURE = "hashing_failure" + MISSING_TABLE_INFO = "missing_table_info" + PROCESSED_ITEMS_CALCULATION = "processed_items_calculation" + PRIMARY_KEY_ISSUE = "primary_key_issue" + + _DynamoDBTableName = str _DynamoDBItemFieldName = str _DynamoDBItemTypeTag = str @@ -101,6 +112,9 @@ class _DynamoDBTransactUpdateItem(TypedDict): ] +_OPERATION_BASE = "DynamoDB." + + def _extract_span_pointers_for_dynamodb_response( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], operation_name: str, @@ -139,50 +153,118 @@ def _extract_span_pointers_for_dynamodb_putitem_response( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], request_parameters: Dict[str, Any], ) -> List[_SpanPointerDescription]: + operation = _OPERATION_BASE + "PutItem" + try: table_name = request_parameters["TableName"] item = request_parameters["Item"] + except KeyError as e: + log.debug( + "failed to extract %s span pointer: missing key %s", + operation, + e, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value + ) + return [] - return [ - _aws_dynamodb_item_span_pointer_description( - pointer_direction=_SpanPointerDirection.DOWNSTREAM, - table_name=table_name, - primary_key=_aws_dynamodb_item_primary_key_from_item( - dynamodb_primary_key_names_for_tables[table_name], item - ), - ) - ] + primary_key_names = _extract_primary_key_names_from_configuration( + operation=operation, + dynamodb_primary_key_names_for_tables=dynamodb_primary_key_names_for_tables, + table_name=table_name, + ) + if primary_key_names is None: + return [] + + primary_key = _aws_dynamodb_item_primary_key_from_item( + operation=operation, + primary_key_field_names=primary_key_names, + item=item, + ) + if primary_key is None: + return [] + + try: + span_pointer_description = _aws_dynamodb_item_span_pointer_description( + operation=operation, + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=primary_key, + ) + if span_pointer_description is None: + return [] + + return [span_pointer_description] except Exception as e: - log.warning( - "failed to generate DynamoDB.PutItem span pointer: %s", - str(e), + log.debug( + "failed to generate %s span pointer: %s", + operation, + e, ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) return [] +def _extract_primary_key_names_from_configuration( + operation: str, + dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], + table_name: _DynamoDBTableName, +) -> Optional[Set[_DynamoDBItemFieldName]]: + try: + return dynamodb_primary_key_names_for_tables[table_name] + except KeyError as e: + log.warning( + "failed to extract %s span pointer: table %s not found in primary key names", + operation, + e, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.MISSING_TABLE_INFO.value + ) + return None + + def _extract_span_pointers_for_dynamodb_keyed_operation_response( operation_name: str, request_parmeters: Dict[str, Any], ) -> List[_SpanPointerDescription]: + operation = _OPERATION_BASE + operation_name + try: table_name = request_parmeters["TableName"] key = request_parmeters["Key"] + except KeyError as e: + log.debug( + "failed to extract %s span pointer: missing key %s", + operation, + e, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value + ) + return [] - return [ - _aws_dynamodb_item_span_pointer_description( - pointer_direction=_SpanPointerDirection.DOWNSTREAM, - table_name=table_name, - primary_key=key, - ) - ] + try: + span_pointer_description = _aws_dynamodb_item_span_pointer_description( + operation=operation, + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=key, + ) + if span_pointer_description is None: + return [] + + return [span_pointer_description] except Exception as e: - log.warning( - "failed to generate DynamoDB.%s span pointer: %s", - operation_name, - str(e), + log.debug( + "failed to generate %s span pointer: %s", + operation, + e, ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) return [] @@ -191,33 +273,59 @@ def _extract_span_pointers_for_dynamodb_batchwriteitem_response( request_parameters: Dict[str, Any], response: Dict[str, Any], ) -> List[_SpanPointerDescription]: + operation = _OPERATION_BASE + "BatchWriteItem" + try: requested_items = request_parameters["RequestItems"] unprocessed_items = response.get("UnprocessedItems", {}) processed_items = _identify_dynamodb_batch_write_item_processed_items(requested_items, unprocessed_items) + if processed_items is None: + return [] - return list( - itertools.chain.from_iterable( - [ - _aws_dynamodb_item_span_pointer_description( - pointer_direction=_SpanPointerDirection.DOWNSTREAM, - table_name=table_name, - primary_key=_aws_dynamodb_item_primary_key_from_write_request( - dynamodb_primary_key_names_for_tables, table_name, write_request - ), - ) - for write_request in processed_items_for_table - ] - for table_name, processed_items_for_table in processed_items.items() - ) + except Exception as e: + log.debug( + "failed to extract %s span pointers: %s", + operation, + e, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) + return [] + + try: + result = [] + for table_name, processed_items_for_table in processed_items.items(): + for write_request in processed_items_for_table: + primary_key = _aws_dynamodb_item_primary_key_from_write_request( + dynamodb_primary_key_names_for_tables=dynamodb_primary_key_names_for_tables, + table_name=table_name, + write_request=write_request, + ) + if primary_key is None: + return [] + + span_pointer_description = _aws_dynamodb_item_span_pointer_description( + operation=operation, + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=primary_key, + ) + if span_pointer_description is None: + return [] + + result.append(span_pointer_description) + + return result except Exception as e: - log.warning( - "failed to generate DynamoDB.BatchWriteItem span pointer: %s", - str(e), + log.debug( + "failed to generate %s span pointer: %s", + operation, + e, ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) return [] @@ -225,6 +333,7 @@ def _extract_span_pointers_for_dynamodb_transactwriteitems_response( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], request_parameters: Dict[str, Any], ) -> List[_SpanPointerDescription]: + operation = _OPERATION_BASE + "TransactWriteItems" try: return list( itertools.chain.from_iterable( @@ -237,21 +346,29 @@ def _extract_span_pointers_for_dynamodb_transactwriteitems_response( ) except Exception as e: - log.warning( - "failed to generate DynamoDB.TransactWriteItems span pointer: %s", - str(e), + log.debug( + "failed to generate %s span pointer: %s", + operation, + e, ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) return [] def _identify_dynamodb_batch_write_item_processed_items( requested_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], unprocessed_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]], -) -> Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]]: +) -> Optional[Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]]]: + operation = _OPERATION_BASE + "BatchWriteItem" + processed_items = {} if not all(table_name in requested_items for table_name in unprocessed_items): - raise ValueError("unprocessed items include tables not in the requested items") + log.debug("%s unprocessed items include tables not in the requested items", operation) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PROCESSED_ITEMS_CALCULATION.value + ) + return None for table_name, requested_write_requests in requested_items.items(): if table_name not in unprocessed_items: @@ -262,7 +379,14 @@ def _identify_dynamodb_batch_write_item_processed_items( unprocessed_write_request in requested_write_requests for unprocessed_write_request in unprocessed_items[table_name] ): - raise ValueError("unprocessed write requests include items not in the requested write requests") + log.debug( + "%s unprocessed write requests include items not in the requested write requests", + operation, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PROCESSED_ITEMS_CALCULATION.value + ) + return None these_processed_items = [ deepcopy(processed_write_request) @@ -277,29 +401,45 @@ def _identify_dynamodb_batch_write_item_processed_items( def _aws_dynamodb_item_primary_key_from_item( + operation: str, primary_key_field_names: Set[_DynamoDBItemFieldName], item: _DynamoDBItem, -) -> _DynamoDBItemPrimaryKey: +) -> Optional[_DynamoDBItemPrimaryKey]: if len(primary_key_field_names) not in (1, 2): - raise ValueError(f"unexpected number of primary key fields: {len(primary_key_field_names)}") + log.debug("unexpected number of primary key fields: %d", len(primary_key_field_names)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None - return { - primary_key_field_name: _aws_dynamodb_extract_and_verify_primary_key_field_value_item( - item, primary_key_field_name + result = {} + for primary_key_field_name in primary_key_field_names: + primary_key_field_value = _aws_dynamodb_extract_and_verify_primary_key_field_value_item( + operation, item, primary_key_field_name ) - for primary_key_field_name in primary_key_field_names - } + if primary_key_field_value is None: + return None + + result[primary_key_field_name] = primary_key_field_value + + return result def _aws_dynamodb_item_primary_key_from_write_request( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], table_name: _DynamoDBTableName, write_request: _DynamoDBWriteRequest, -) -> _DynamoDBItemPrimaryKey: +) -> Optional[_DynamoDBItemPrimaryKey]: # https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_WriteRequest.html + operation = _OPERATION_BASE + "BatchWriteItem" + if len(write_request) != 1: - raise ValueError(f"unexpected number of write request fields: {len(write_request)}") + log.debug("unexpected number of write request fields: %d", len(write_request)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value + ) + return None if "PutRequest" in write_request: # Unfortunately mypy doesn't properly see the if statement above as a @@ -307,9 +447,18 @@ def _aws_dynamodb_item_primary_key_from_write_request( # _DynamoDBPutRequestWriteRequest, so we help it out ourselves. write_request = cast(_DynamoDBPutRequestWriteRequest, write_request) + primary_key_field_names = _extract_primary_key_names_from_configuration( + operation=operation, + dynamodb_primary_key_names_for_tables=dynamodb_primary_key_names_for_tables, + table_name=table_name, + ) + if primary_key_field_names is None: + return None + return _aws_dynamodb_item_primary_key_from_item( - dynamodb_primary_key_names_for_tables[table_name], - write_request["PutRequest"]["Item"], + operation=operation, + primary_key_field_names=primary_key_field_names, + item=write_request["PutRequest"]["Item"], ) elif "DeleteRequest" in write_request: @@ -321,15 +470,25 @@ def _aws_dynamodb_item_primary_key_from_write_request( return write_request["DeleteRequest"]["Key"] else: - raise ValueError(f"unexpected write request structure: {''.join(sorted(write_request.keys()))}") + log.debug("unexpected write request structure: %s", "".join(sorted(write_request.keys()))) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value + ) + return None def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( dynamodb_primary_key_names_for_tables: Dict[_DynamoDBTableName, Set[_DynamoDBItemFieldName]], transact_write_request: _DynamoDBTransactWriteItem, ) -> List[_SpanPointerDescription]: + operation = _OPERATION_BASE + "TransactWriteItems" + if len(transact_write_request) != 1: - raise ValueError(f"unexpected number of transact write request fields: {len(transact_write_request)}") + log.debug("unexpected number of transact write request fields: %d", len(transact_write_request)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value + ) + return [] if "ConditionCheck" in transact_write_request: # ConditionCheck requests don't actually modify anything, so we don't @@ -354,10 +513,23 @@ def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( transact_write_request = cast(_DynamoDBTransactPutItem, transact_write_request) table_name = transact_write_request["Put"]["TableName"] - key = _aws_dynamodb_item_primary_key_from_item( - dynamodb_primary_key_names_for_tables[table_name], - transact_write_request["Put"]["Item"], + + primary_key_field_names = _extract_primary_key_names_from_configuration( + operation=operation, + dynamodb_primary_key_names_for_tables=dynamodb_primary_key_names_for_tables, + table_name=table_name, + ) + if primary_key_field_names is None: + return [] + + primary_key = _aws_dynamodb_item_primary_key_from_item( + operation=operation, + primary_key_field_names=primary_key_field_names, + item=transact_write_request["Put"]["Item"], ) + if primary_key is None: + return [] + key = primary_key elif "Update" in transact_write_request: # Unfortunately mypy does not properly see the if statement above as a @@ -370,94 +542,163 @@ def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( key = transact_write_request["Update"]["Key"] else: - raise ValueError( - f"unexpected transact write request structure: {''.join(sorted(transact_write_request.keys()))}" + log.debug("unexpected transact write request structure: %s", "".join(sorted(transact_write_request.keys()))) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) + return [] - return [ - _aws_dynamodb_item_span_pointer_description( - pointer_direction=_SpanPointerDirection.DOWNSTREAM, - table_name=table_name, - primary_key=key, - ) - ] + span_pointer_description = _aws_dynamodb_item_span_pointer_description( + operation=operation, + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + table_name=table_name, + primary_key=key, + ) + if span_pointer_description is None: + return [] + + return [span_pointer_description] def _aws_dynamodb_item_span_pointer_description( + operation: str, pointer_direction: _SpanPointerDirection, table_name: _DynamoDBTableName, primary_key: _DynamoDBItemPrimaryKey, -) -> _SpanPointerDescription: +) -> Optional[_SpanPointerDescription]: + pointer_hash = _aws_dynamodb_item_span_pointer_hash(operation, table_name, primary_key) + if pointer_hash is None: + return None + return _SpanPointerDescription( pointer_kind="aws.dynamodb.item", pointer_direction=pointer_direction, - pointer_hash=_aws_dynamodb_item_span_pointer_hash(table_name, primary_key), + pointer_hash=pointer_hash, extra_attributes={}, ) def _aws_dynamodb_extract_and_verify_primary_key_field_value_item( + operation: str, item: _DynamoDBItem, primary_key_field_name: _DynamoDBItemFieldName, -) -> _DynamoDBItemPrimaryKeyValue: +) -> Optional[_DynamoDBItemPrimaryKeyValue]: if primary_key_field_name not in item: - raise ValueError(f"missing primary key field: {primary_key_field_name}") + log.debug("missing primary key field: %s", primary_key_field_name) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None value_object = item[primary_key_field_name] if len(value_object) != 1: - raise ValueError(f"primary key field {primary_key_field_name} must have exactly one value: {len(value_object)}") + log.debug("primary key field %s must have exactly one value: %d", primary_key_field_name, len(value_object)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None value_type, value_data = next(iter(value_object.items())) if value_type not in ("S", "N", "B"): - raise ValueError(f"unexpected primary key field {primary_key_field_name} value type: {value_type}") + log.debug("unexpected primary key field %s value type: %s", primary_key_field_name, value_type) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None if not isinstance(value_data, str): - raise ValueError(f"unexpected primary key field {primary_key_field_name} value data type: {type(value_data)}") + log.debug("unexpected primary key field %s value data type: %s", primary_key_field_name, type(value_data)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None return {value_type: value_data} -def _aws_dynamodb_item_span_pointer_hash(table_name: _DynamoDBTableName, primary_key: _DynamoDBItemPrimaryKey) -> str: - if len(primary_key) == 1: - key, value_object = next(iter(primary_key.items())) - encoded_key_1 = key.encode("utf-8") - encoded_value_1 = _aws_dynamodb_item_encode_primary_key_value(value_object) - encoded_key_2 = b"" - encoded_value_2 = b"" +def _aws_dynamodb_item_span_pointer_hash( + operation: str, table_name: _DynamoDBTableName, primary_key: _DynamoDBItemPrimaryKey +) -> Optional[str]: + try: + if len(primary_key) == 1: + key, value_object = next(iter(primary_key.items())) + encoded_key_1 = key.encode("utf-8") + + encoded_value_1 = _aws_dynamodb_item_encode_primary_key_value(operation, value_object) + if encoded_value_1 is None: + return None + + encoded_key_2 = b"" + encoded_value_2 = b"" + + elif len(primary_key) == 2: + (key_1, value_object_1), (key_2, value_object_2) = sorted( + primary_key.items(), key=lambda x: x[0].encode("utf-8") + ) + encoded_key_1 = key_1.encode("utf-8") + + encoded_value_1 = _aws_dynamodb_item_encode_primary_key_value(operation, value_object_1) + if encoded_value_1 is None: + return None + + encoded_key_2 = key_2.encode("utf-8") - elif len(primary_key) == 2: - (key_1, value_object_1), (key_2, value_object_2) = sorted( - primary_key.items(), key=lambda x: x[0].encode("utf-8") + maybe_encoded_value_2 = _aws_dynamodb_item_encode_primary_key_value(operation, value_object_2) + if maybe_encoded_value_2 is None: + return None + encoded_value_2 = maybe_encoded_value_2 + + else: + log.debug("unexpected number of primary key fields: %d", len(primary_key)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None + + return _standard_hashing_function( + table_name.encode("utf-8"), + encoded_key_1, + encoded_value_1, + encoded_key_2, + encoded_value_2, ) - encoded_key_1 = key_1.encode("utf-8") - encoded_value_1 = _aws_dynamodb_item_encode_primary_key_value(value_object_1) - encoded_key_2 = key_2.encode("utf-8") - encoded_value_2 = _aws_dynamodb_item_encode_primary_key_value(value_object_2) - else: - raise ValueError(f"unexpected number of primary key fields: {len(primary_key)}") - - return _standard_hashing_function( - table_name.encode("utf-8"), - encoded_key_1, - encoded_value_1, - encoded_key_2, - encoded_value_2, - ) + except Exception as e: + log.debug("failed to generate %s span pointer hash: %s", operation, e) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) + return None -def _aws_dynamodb_item_encode_primary_key_value(value_object: _DynamoDBItemPrimaryKeyValue) -> bytes: - if len(value_object) != 1: - raise ValueError(f"primary key value object must have exactly one field: {len(value_object)}") +def _aws_dynamodb_item_encode_primary_key_value( + operation: str, value_object: _DynamoDBItemPrimaryKeyValue +) -> Optional[bytes]: + try: + if len(value_object) != 1: + log.debug("primary key value object must have exactly one field: %d", len(value_object)) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None + + value_type, value = next(iter(value_object.items())) - value_type, value = next(iter(value_object.items())) + if value_type == "S": + return value.encode("utf-8") - if value_type == "S": - return value.encode("utf-8") + if value_type in ("N", "B"): + # these should already be here as ASCII strings + return value.encode("ascii") - if value_type in ("N", "B"): - # these should already be here as ASCII strings - return value.encode("ascii") + log.debug("unexpected primary key value type: %s", value_type) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None - raise ValueError(f"unknown primary key value type: {value_type}") + except Exception as e: + log.debug("failed to encode primary key value for %s: %s", operation, e) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None diff --git a/ddtrace/_trace/utils_botocore/span_pointers/s3.py b/ddtrace/_trace/utils_botocore/span_pointers/s3.py index dbd56279a7b..46625292da1 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/s3.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/s3.py @@ -1,18 +1,27 @@ +from enum import Enum from typing import Any from typing import Callable from typing import Dict from typing import List from typing import NamedTuple +from typing import Optional from ddtrace._trace._span_pointer import _SpanPointerDescription from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace._span_pointer import _standard_hashing_function +from ddtrace._trace.utils_botocore.span_pointers.telemetry import record_span_pointer_calculation_issue from ddtrace.internal.logger import get_logger log = get_logger(__name__) +class _TelemetryIssueTags(Enum): + REQUEST_PARAMETERS = "request_parameters" + ETAG_QUOTES = "etag_quotes" + HASHING_FAILURE = "hashing_failure" + + def _extract_span_pointers_for_s3_response( operation_name: str, request_parameters: Dict[str, Any], @@ -74,6 +83,8 @@ def _extract_span_pointers_for_s3_response_with_helper( request_parameters: Dict[str, Any], response: Dict[str, Any], ) -> List[_SpanPointerDescription]: + operation = f"S3.{operation_name}" + try: hashing_properties = extractor(request_parameters, response) bucket = hashing_properties.bucket @@ -84,54 +95,71 @@ def _extract_span_pointers_for_s3_response_with_helper( if etag.startswith('"') and etag.endswith('"'): etag = etag[1:-1] - except KeyError as e: - log.warning( - "missing a parameter or response field required to make span pointer for S3.%s: %s", - operation_name, - str(e), + except Exception as e: + log.debug( + "problem with parameters for %s span pointer: %s", + operation, + e, + ) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) return [] - try: - return [ - _aws_s3_object_span_pointer_description( - pointer_direction=_SpanPointerDirection.DOWNSTREAM, - bucket=bucket, - key=key, - etag=etag, - ) - ] - except Exception as e: - log.warning( - "failed to generate S3.%s span pointer: %s", - operation_name, - str(e), - ) + span_pointer_description = _aws_s3_object_span_pointer_description( + operation=operation, + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + bucket=bucket, + key=key, + etag=etag, + ) + if span_pointer_description is None: return [] + return [span_pointer_description] + def _aws_s3_object_span_pointer_description( + operation: str, pointer_direction: _SpanPointerDirection, bucket: str, key: str, etag: str, -) -> _SpanPointerDescription: +) -> Optional[_SpanPointerDescription]: + pointer_hash = _aws_s3_object_span_pointer_hash(operation, bucket, key, etag) + if pointer_hash is None: + return None + return _SpanPointerDescription( pointer_kind="aws.s3.object", pointer_direction=pointer_direction, - pointer_hash=_aws_s3_object_span_pointer_hash(bucket, key, etag), + pointer_hash=pointer_hash, extra_attributes={}, ) -def _aws_s3_object_span_pointer_hash(bucket: str, key: str, etag: str) -> str: +def _aws_s3_object_span_pointer_hash(operation: str, bucket: str, key: str, etag: str) -> Optional[str]: if '"' in etag: # Some AWS API endpoints put the ETag in double quotes. We expect the # calling code to have correctly fixed this already. - raise ValueError(f"ETag should not have double quotes: {etag}") + log.debug( + "ETag should not have double quotes: %s", + etag, + ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.ETAG_QUOTES.value) + return None - return _standard_hashing_function( - bucket.encode("ascii"), - key.encode("utf-8"), - etag.encode("ascii"), - ) + try: + return _standard_hashing_function( + bucket.encode("ascii"), + key.encode("utf-8"), + etag.encode("ascii"), + ) + + except Exception as e: + log.debug( + "failed to hash S3 object span pointer: %s", + e, + ) + record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) + return None diff --git a/ddtrace/_trace/utils_botocore/span_pointers/telemetry.py b/ddtrace/_trace/utils_botocore/span_pointers/telemetry.py new file mode 100644 index 00000000000..ccb0dce9a25 --- /dev/null +++ b/ddtrace/_trace/utils_botocore/span_pointers/telemetry.py @@ -0,0 +1,18 @@ +from ddtrace._trace.telemetry import record_span_pointer_calculation as base_record_span_pointer_calculation +from ddtrace._trace.telemetry import record_span_pointer_calculation_issue as base_record_span_pointer_calculation_issue + + +_CONTEXT = "botocore" + + +def record_span_pointer_calculation(span_pointer_count: int) -> None: + base_record_span_pointer_calculation( + context=_CONTEXT, + span_pointer_count=span_pointer_count, + ) + + +def record_span_pointer_calculation_issue(operation: str, issue_tag: str) -> None: + base_record_span_pointer_calculation_issue( + context=_CONTEXT, additional_tags=(("operation", operation), ("issue", issue_tag)) + ) diff --git a/releasenotes/notes/reduce-span-pointer-noise-e1cf01ec581a409c.yaml b/releasenotes/notes/reduce-span-pointer-noise-e1cf01ec581a409c.yaml new file mode 100644 index 00000000000..180b3e13e75 --- /dev/null +++ b/releasenotes/notes/reduce-span-pointer-noise-e1cf01ec581a409c.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + botocore: This fix resolves an issue where our span pointer calculation code added recently logged unactionable messages. diff --git a/tests/tracer/test_telemetry.py b/tests/tracer/test_telemetry.py new file mode 100644 index 00000000000..d400966108a --- /dev/null +++ b/tests/tracer/test_telemetry.py @@ -0,0 +1,67 @@ +from typing import NamedTuple + +import pytest + +from ddtrace._trace.telemetry import _span_pointer_count_to_tag + + +def test_span_pointer_count_to_tag_returns_strings() -> None: + unique_tags = set() + + for count in range(-10, 500): + tag = _span_pointer_count_to_tag(count) + + assert isinstance(tag, str) + assert tag != "" + + unique_tags.add(tag) + + reasonable_cadinality_limit = 15 + assert len(unique_tags) <= reasonable_cadinality_limit + + +class SpanPointerTagCase(NamedTuple): + count: int + expected: str + + +@pytest.mark.parametrize( + "test_case", + [ + SpanPointerTagCase( + count=-1, + expected="negative", + ), + SpanPointerTagCase( + count=0, + expected="0", + ), + SpanPointerTagCase( + count=1, + expected="1", + ), + SpanPointerTagCase( + count=5, + expected="5", + ), + SpanPointerTagCase( + count=15, + expected="11-20", + ), + SpanPointerTagCase( + count=25, + expected="21-50", + ), + SpanPointerTagCase( + count=95, + expected="51-100", + ), + SpanPointerTagCase( + count=1000, + expected="101+", + ), + ], + ids=lambda test_case: f"count={test_case.count}", +) +def test_span_pointer_count_to_tag(test_case: SpanPointerTagCase) -> None: + assert _span_pointer_count_to_tag(test_case.count) == test_case.expected diff --git a/tests/tracer/utils_botocore/test_span_pointers.py b/tests/tracer/utils_botocore/test_span_pointers.py index 49209c4fc47..775ad23d6bd 100644 --- a/tests/tracer/utils_botocore/test_span_pointers.py +++ b/tests/tracer/utils_botocore/test_span_pointers.py @@ -58,6 +58,7 @@ class HashingCase(NamedTuple): def test_hashing(self, hashing_case: HashingCase) -> None: assert ( _aws_s3_object_span_pointer_hash( + operation="SomeOperation", bucket=hashing_case.bucket, key=hashing_case.key, etag=hashing_case.etag, @@ -118,6 +119,7 @@ class HashingCase(NamedTuple): def test_hashing(self, hashing_case: HashingCase) -> None: assert ( _aws_dynamodb_item_span_pointer_hash( + operation="SomeOperation", table_name=hashing_case.table_name, primary_key=hashing_case.primary_key, ) @@ -133,7 +135,8 @@ class PointersCase(NamedTuple): request_parameters: dict response: dict expected_pointers: List[_SpanPointerDescription] - expected_warning_regex: Optional[str] + expected_logger_regex: Optional[str] + logger_level: str @pytest.mark.parametrize( "pointers_case", @@ -145,7 +148,8 @@ class PointersCase(NamedTuple): request_parameters={}, response={}, expected_pointers=[], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="unknown s3 operation", @@ -154,7 +158,8 @@ class PointersCase(NamedTuple): request_parameters={}, response={}, expected_pointers=[], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="malformed s3.PutObject, missing bucket", @@ -167,7 +172,8 @@ class PointersCase(NamedTuple): "ETag": "ab12ef34", }, expected_pointers=[], - expected_warning_regex=r"missing a parameter or response field .*: 'Bucket'", + expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'Bucket'", + logger_level="debug", ), PointersCase( name="malformed s3.PutObject, missing key", @@ -180,7 +186,8 @@ class PointersCase(NamedTuple): "ETag": "ab12ef34", }, expected_pointers=[], - expected_warning_regex=r"missing a parameter or response field .*: 'Key'", + expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'Key'", + logger_level="debug", ), PointersCase( name="malformed s3.PutObject, missing etag", @@ -192,7 +199,8 @@ class PointersCase(NamedTuple): }, response={}, expected_pointers=[], - expected_warning_regex=r"missing a parameter or response field .*: 'ETag'", + expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'ETag'", + logger_level="debug", ), PointersCase( name="malformed s3.PutObject, impossible non-ascii bucket", @@ -206,7 +214,8 @@ class PointersCase(NamedTuple): "ETag": "ab12ef34", }, expected_pointers=[], - expected_warning_regex=r".*'ascii' codec can't encode characters.*", + expected_logger_regex=r".*'ascii' codec can't encode characters.*", + logger_level="debug", ), PointersCase( name="s3.PutObject", @@ -227,7 +236,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="s3.PutObject with double quoted ETag", @@ -249,7 +259,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="s3.CopyObject", @@ -272,7 +283,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="s3.CopyObject with double quoted ETag", @@ -295,7 +307,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="s3.CompleteMultipartUpload", @@ -316,7 +329,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="s3.CompleteMultipartUpload with double quoted ETag", @@ -338,7 +352,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.PutItem", @@ -361,7 +376,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.PutItem with extra data", @@ -385,7 +401,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.PutItem unknown table", @@ -401,7 +418,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*unknown-table.*", + expected_logger_regex=".*unknown-table.*", + logger_level="warning", ), PointersCase( name="dynamodb.PutItem missing primary key", @@ -417,7 +435,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*missing primary key field: some-key", + expected_logger_regex=".*missing primary key field: some-key", + logger_level="debug", ), PointersCase( name="dynamodb.UpdateItem", @@ -440,7 +459,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.UpdateItem table does not need to be known", @@ -463,7 +483,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.UpdateItem with two key attributes", @@ -487,7 +508,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.UpdateItem with three keys, impossibly", @@ -505,7 +527,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*unexpected number of primary key fields: 3", + expected_logger_regex=".*unexpected number of primary key fields: 3", + logger_level="debug", ), PointersCase( name="dynamodb.UpdateItem missing the key", @@ -518,7 +541,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*'Key'.*", + expected_logger_regex=".*'Key'.*", + logger_level="debug", ), PointersCase( name="dynamodb.DeleteItem", @@ -541,7 +565,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.DeleteItem table does not need to be known", @@ -564,7 +589,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.DeleteItem with two key attributes", @@ -588,7 +614,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.DeleteItem with three keys, impossibly", @@ -606,7 +633,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*unexpected number of primary key fields: 3", + expected_logger_regex=".*unexpected number of primary key fields: 3", + logger_level="debug", ), PointersCase( name="dynamodb.DeleteItem missing the key", @@ -619,7 +647,8 @@ class PointersCase(NamedTuple): # things we do not care about }, expected_pointers=[], - expected_warning_regex=".*'Key'.*", + expected_logger_regex=".*'Key'.*", + logger_level="debug", ), PointersCase( name="dynamodb.BatchWriteItem works with multiple items and tables", @@ -697,7 +726,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.BatchWriteItem still needs the mapping sometimes", @@ -718,7 +748,8 @@ class PointersCase(NamedTuple): }, response={}, expected_pointers=[], - expected_warning_regex=".*unknown-table.*", + expected_logger_regex=".*unknown-table.*", + logger_level="warning", ), PointersCase( name="dynamodb.TransactWriteItems basic case", @@ -787,7 +818,8 @@ class PointersCase(NamedTuple): extra_attributes={}, ), ], - expected_warning_regex=None, + expected_logger_regex=None, + logger_level="debug", ), PointersCase( name="dynamodb.TransactWriteItems still needs the mapping sometimes", @@ -807,7 +839,8 @@ class PointersCase(NamedTuple): }, response={}, expected_pointers=[], - expected_warning_regex=".*unknown-table.*", + expected_logger_regex=".*unknown-table.*", + logger_level="warning", ), ], ids=lambda case: case.name, @@ -816,7 +849,7 @@ def test_pointers(self, pointers_case: PointersCase) -> None: # We might like to use caplog here but it resulted in inconsistent test # behavior, so we have to go a bit deeper. - with mock.patch.object(logging.Logger, "warning") as mock_logger: + with mock.patch.object(logging.Logger, pointers_case.logger_level) as mock_logger: assert sorted( extract_span_pointers_from_successful_botocore_response( dynamodb_primary_key_names_for_tables={ @@ -830,7 +863,7 @@ def test_pointers(self, pointers_case: PointersCase) -> None: key=lambda pointer: pointer.pointer_hash, ) == sorted(pointers_case.expected_pointers, key=lambda pointer: pointer.pointer_hash) - if pointers_case.expected_warning_regex is None: + if pointers_case.expected_logger_regex is None: mock_logger.assert_not_called() else: @@ -840,7 +873,7 @@ def test_pointers(self, pointers_case: PointersCase) -> None: assert not kwargs fmt, *other_args = args assert re.match( - pointers_case.expected_warning_regex, + pointers_case.expected_logger_regex, fmt % tuple(other_args), ) @@ -851,7 +884,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): table_name: str write_request: _DynamoDBWriteRequest primary_key: Optional[Dict[str, Dict[str, str]]] - expected_exception_regex: Optional[str] + expected_logger_regex: Optional[str] @pytest.mark.parametrize( "test_case", @@ -868,7 +901,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key={"some-key": {"S": "some-value"}}, - expected_exception_regex=None, + expected_logger_regex=None, ), WriteRequestPrimaryKeyCase( name="delete request", @@ -881,7 +914,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key={"some-key": {"S": "some-value"}}, - expected_exception_regex=None, + expected_logger_regex=None, ), WriteRequestPrimaryKeyCase( name="impossible combined request", @@ -900,7 +933,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key=None, - expected_exception_regex="unexpected number of write request fields", + expected_logger_regex="unexpected number of write request fields", ), WriteRequestPrimaryKeyCase( name="unknown request kind", @@ -914,23 +947,13 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key=None, - expected_exception_regex="unexpected write request structure: SomeRequest", + expected_logger_regex="unexpected write request structure: SomeRequest", ), ], ids=lambda test_case: test_case.name, ) def test_aws_dynamodb_item_primary_key_from_write_request(self, test_case: WriteRequestPrimaryKeyCase) -> None: - if test_case.expected_exception_regex is not None: - with pytest.raises(ValueError, match=test_case.expected_exception_regex): - _aws_dynamodb_item_primary_key_from_write_request( - dynamodb_primary_key_names_for_tables={ - "some-table": {"some-key"}, - }, - table_name=test_case.table_name, - write_request=test_case.write_request, - ) - - else: + with mock.patch.object(logging.Logger, "debug") as mock_logger: assert ( _aws_dynamodb_item_primary_key_from_write_request( dynamodb_primary_key_names_for_tables={ @@ -942,12 +965,26 @@ def test_aws_dynamodb_item_primary_key_from_write_request(self, test_case: Write == test_case.primary_key ) + if test_case.expected_logger_regex is None: + mock_logger.assert_not_called() + + else: + mock_logger.assert_called_once() + + (args, kwargs) = mock_logger.call_args + assert not kwargs + fmt, *other_args = args + assert re.match( + test_case.expected_logger_regex, + fmt % tuple(other_args), + ) + class ProcessedWriteRequestCase(NamedTuple): name: str requested_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]] unprocessed_items: Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]] expected_processed_items: Optional[Dict[_DynamoDBTableName, List[_DynamoDBWriteRequest]]] - expected_exception_regex: Optional[str] + expected_logger_regex: Optional[str] @pytest.mark.parametrize( "test_case", @@ -977,7 +1014,7 @@ class ProcessedWriteRequestCase(NamedTuple): }, ], }, - expected_exception_regex=None, + expected_logger_regex=None, ), ProcessedWriteRequestCase( name="all unprocessed", @@ -1004,7 +1041,7 @@ class ProcessedWriteRequestCase(NamedTuple): ], }, expected_processed_items={}, - expected_exception_regex=None, + expected_logger_regex=None, ), ProcessedWriteRequestCase( name="some unprocessed", @@ -1050,7 +1087,7 @@ class ProcessedWriteRequestCase(NamedTuple): }, ], }, - expected_exception_regex=None, + expected_logger_regex=None, ), ProcessedWriteRequestCase( name="nothing unprocessed", @@ -1077,7 +1114,7 @@ class ProcessedWriteRequestCase(NamedTuple): }, ], }, - expected_exception_regex=None, + expected_logger_regex=None, ), ProcessedWriteRequestCase( name="extra unprocessed tables", @@ -1094,7 +1131,7 @@ class ProcessedWriteRequestCase(NamedTuple): ], }, expected_processed_items=None, - expected_exception_regex="unprocessed items include tables not in the requested items", + expected_logger_regex=".*unprocessed items include tables not in the requested items", ), ProcessedWriteRequestCase( name="extra unprocessed items", @@ -1128,26 +1165,34 @@ class ProcessedWriteRequestCase(NamedTuple): ], }, expected_processed_items=None, - expected_exception_regex="unprocessed write requests include items not in the requested write requests", + expected_logger_regex=".*unprocessed write requests include items not in the requested write requests", ), ], ids=lambda test_case: test_case.name, ) def test_identify_dynamodb_batch_write_item_processed_items(self, test_case: ProcessedWriteRequestCase) -> None: - if test_case.expected_exception_regex is not None: - with pytest.raises(Exception, match=test_case.expected_exception_regex): - _identify_dynamodb_batch_write_item_processed_items( - requested_items=test_case.requested_items, - unprocessed_items=test_case.unprocessed_items, - ) + with mock.patch.object(logging.Logger, "debug") as mock_logger: + processed_items = _identify_dynamodb_batch_write_item_processed_items( + requested_items=test_case.requested_items, + unprocessed_items=test_case.unprocessed_items, + ) + assert processed_items == test_case.expected_processed_items - return + if test_case.expected_logger_regex is None: + mock_logger.assert_not_called() - processed_items = _identify_dynamodb_batch_write_item_processed_items( - requested_items=test_case.requested_items, - unprocessed_items=test_case.unprocessed_items, - ) - assert processed_items == test_case.expected_processed_items + else: + mock_logger.assert_called_once() + + (args, kwargs) = mock_logger.call_args + assert not kwargs + fmt, *other_args = args + assert re.match( + test_case.expected_logger_regex, + fmt % tuple(other_args), + ) + + return def collect_all_ids(thing: object, accumulator: Set[int]) -> None: if isinstance(thing, dict): From 8e9edd3c18a7dcd96a6b0482981c12fca902a13c Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 15 Nov 2024 23:09:50 +0000 Subject: [PATCH 171/372] chore(debugger): better no local error message (#11405) We improve the error message reported when an expression fails to resolve a reference to a local variable. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_expressions.py | 15 ++++++++++----- tests/debugging/test_debugger.py | 6 +++--- tests/debugging/test_debugger_span_decoration.py | 10 +++++++--- tests/debugging/test_expressions.py | 6 +++--- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ddtrace/debugging/_expressions.py b/ddtrace/debugging/_expressions.py index 5c50a97d12c..50028b9c6d2 100644 --- a/ddtrace/debugging/_expressions.py +++ b/ddtrace/debugging/_expressions.py @@ -86,6 +86,13 @@ def instanceof(value: Any, type_qname: str) -> bool: return False +def get_local(_locals: Mapping[str, Any], name: str) -> Any: + try: + return _locals[name] + except KeyError: + raise NameError(f"No such local variable: '{name}'") + + class DDCompiler: @classmethod def __getmember__(cls, o, a): @@ -235,11 +242,9 @@ def _compile_direct_operation(self, ast: DDASTType) -> Optional[List[Instr]]: if arg == "@it": return [Instr("LOAD_FAST", "_dd_it")] - return [ - Instr("LOAD_FAST", "_locals"), - Instr("LOAD_CONST", self.__ref__(arg)), - Instr("BINARY_SUBSCR"), - ] + return self._call_function( + get_local, [Instr("LOAD_FAST", "_locals")], [Instr("LOAD_CONST", self.__ref__(arg))] + ) return None diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index ad2771cfb71..2a0bdaf13d8 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -772,7 +772,7 @@ def test_debugger_condition_eval_error_get_reported_once(): evaluationErrors = snapshot["debugger"]["snapshot"]["evaluationErrors"] assert 1 == len(evaluationErrors) assert "foo == 42" == evaluationErrors[0]["expr"] - assert "'foo'" == evaluationErrors[0]["message"] + assert "No such local variable: 'foo'" == evaluationErrors[0]["message"] def test_debugger_function_probe_eval_on_entry(): @@ -881,6 +881,7 @@ def test_debugger_log_line_probe_generate_messages(): probe_id="foo", source_file="tests/submod/stuff.py", line=36, + rate=float("inf"), **compile_template( "hello world ", {"dsl": "foo", "json": {"ref": "foo"}}, @@ -899,8 +900,7 @@ def test_debugger_log_line_probe_generate_messages(): assert "hello world ERROR 456!" == msg2["message"], msg2 assert "foo" == msg1["debugger"]["snapshot"]["evaluationErrors"][0]["expr"], msg1 - # not amazing error message for a missing variable - assert "'foo'" == msg1["debugger"]["snapshot"]["evaluationErrors"][0]["message"], msg1 + assert "No such local variable: 'foo'" == msg1["debugger"]["snapshot"]["evaluationErrors"][0]["message"], msg1 assert not msg1["debugger"]["snapshot"]["captures"] diff --git a/tests/debugging/test_debugger_span_decoration.py b/tests/debugging/test_debugger_span_decoration.py index fd6e8d2aca7..5d7e51ee6a7 100644 --- a/tests/debugging/test_debugger_span_decoration.py +++ b/tests/debugging/test_debugger_span_decoration.py @@ -65,7 +65,7 @@ def test_debugger_span_decoration_probe_on_inner_function_active_span(self): assert span.get_tag("_dd.di.test.tag.probe_id") == "span-decoration" assert ( span.get_tag("_dd.di.test.bad.evaluation_error") - == "'Failed to evaluate expression \"test\": \\'notathing\\''" + == "'Failed to evaluate expression \"test\": No such local variable: \\'notathing\\''" ) assert not d.test_queue @@ -107,11 +107,15 @@ def test_debugger_span_decoration_probe_on_inner_function_active_span_unconditio assert span.get_tag("_dd.di.test.tag.probe_id") == "span-decoration" assert ( span.get_tag("_dd.di.test.bad.evaluation_error") - == "'Failed to evaluate expression \"test\": \\'notathing\\''" + == "'Failed to evaluate expression \"test\": No such local variable: \\'notathing\\''" ) (signal,) = d.test_queue - assert signal.errors == [EvaluationError(expr="test", message="Failed to evaluate condition: 'notathing'")] + assert signal.errors == [ + EvaluationError( + expr="test", message="Failed to evaluate condition: No such local variable: 'notathing'" + ) + ] (payload,) = d.uploader.wait_for_payloads() assert payload["message"] == "Condition evaluation errors for probe span-decoration" diff --git a/tests/debugging/test_expressions.py b/tests/debugging/test_expressions.py index 1ab4e539c04..f064a951893 100644 --- a/tests/debugging/test_expressions.py +++ b/tests/debugging/test_expressions.py @@ -50,7 +50,7 @@ def __getitem__(self, name): ({"len": {"getmember": [{"ref": "self"}, "collectionField"]}}, {"self": CustomObject("expr")}, 10), ({"len": {"getmember": [{"ref": "self"}, "_privateField"]}}, {"self": CustomObject("expr")}, len("private")), ({"len": {"getmember": [{"ref": "self"}, "bogusField"]}}, {"self": CustomObject("expr")}, AttributeError), - ({"len": {"ref": "payload"}}, {}, KeyError), + ({"len": {"ref": "payload"}}, {}, NameError), # Test plain references ({"ref": "hits"}, {"hits": 42}, 42), ({"getmember": [{"ref": "self"}, "name"]}, {"self": CustomObject("test-me")}, "test-me"), @@ -104,8 +104,8 @@ def __getitem__(self, name): (True, {}, True), ({"or": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 42}, 42), ({"and": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 0}, 0), - ({"or": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 0}, KeyError), - ({"and": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 42}, KeyError), + ({"or": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 0}, NameError), + ({"and": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 42}, NameError), ({"isDefined": "foobar"}, {"bar": 42}, False), ({"isDefined": "bar"}, {"bar": 42}, True), ({"instanceof": [{"ref": "bar"}, "int"]}, {"bar": 42}, True), From f18fc0ecc938513cdd5c35bc96679bff73ea7669 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:11:37 -0600 Subject: [PATCH 172/372] chore(profiling): fix sanitizers on Linux (#11424) In order for our tests to work with sanitizers, * sanitizer lib needs to be propagated from the test fixture to the dynamic libraries (e.g., `dd_wrapper`) (this was broken due to underspecifying the link type) * dynamic linker needs to be able to find the .so; the way we did it worked OK on macos, but on Linux the libs are actually in a subdir ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../profiling/cmake/AnalysisFunc.cmake | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake index d462d25e573..7ea9247d45b 100644 --- a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake @@ -58,15 +58,24 @@ function(add_ddup_config target) # Some sanitizers (or the analysis--such as symbolization--tooling thereof) work better with frame pointers, so # we include it here. target_compile_options(${target} PRIVATE -fsanitize=${SANITIZE_OPTIONS} -fno-omit-frame-pointer) - target_link_options(${target} PRIVATE -fsanitize=${SANITIZE_OPTIONS}) + target_link_options(${target} PRIVATE -fsanitize=${SANITIZE_OPTIONS} -shared-libsan) - # We need to do a little bit of work in order to ensure the dynamic *san libraries can be linked Procedure - # adapted from datadog/ddprof :) - execute_process( - COMMAND ${CMAKE_CXX_COMPILER} -print-file-name= - OUTPUT_VARIABLE LIBSAN_LIB_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) - set_target_properties(${target} PROPERTIES INSTALL_RPATH ${LIBSAN_LIB_PATH} BUILD_RPATH ${LIBSAN_LIB_PATH}) + # Locate all directories containing relevant `.so` files + execute_process( + COMMAND bash -c "find $(${CMAKE_CXX_COMPILER} -print-file-name=) -name '*.so' -exec dirname {} \; | uniq" + OUTPUT_VARIABLE LIBSAN_LIB_PATHS + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) + + # Print for debugging + message(STATUS "LIBSAN_LIB_PATHS: ${LIBSAN_LIB_PATHS}") + + # Split the paths into a semicolon-separated list for CMake + string(REPLACE "\n" ";" LIBSAN_LIB_PATHS_LIST "${LIBSAN_LIB_PATHS}") + + # Set RPATH to include all identified paths + set_target_properties(${target} PROPERTIES + BUILD_RPATH "${LIBSAN_LIB_PATHS_LIST}" + INSTALL_RPATH "${LIBSAN_LIB_PATHS_LIST}") endif() # If DO_FANALYZER is specified and we're using gcc, then we can use -fanalyzer From 343ba22bbe4010edb9206950fca5dc21c35d3d58 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 18 Nov 2024 11:37:20 +0100 Subject: [PATCH 173/372] fix(iast): add some modules to the denylist (#11418) ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_iast/_ast/ast_patching.py | 4 +++- releasenotes/notes/umap-learn-denylist-b7c55f42f2408c24.yaml | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/umap-learn-denylist-b7c55f42f2408c24.yaml diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 6dbad8752b6..4ae0c31233e 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -302,6 +302,9 @@ "httpcore.", "google.auth.", "googlecloudsdk.", + "umap.", + "pynndescent.", + "numba.", ) @@ -367,7 +370,6 @@ def visit_ast( module_name: Text = "", ) -> Optional[str]: parsed_ast = ast.parse(source_text, module_path) - _VISITOR.update_location(filename=module_path, module_name=module_name) modified_ast = _VISITOR.visit(parsed_ast) diff --git a/releasenotes/notes/umap-learn-denylist-b7c55f42f2408c24.yaml b/releasenotes/notes/umap-learn-denylist-b7c55f42f2408c24.yaml new file mode 100644 index 00000000000..5a31dfdcfd9 --- /dev/null +++ b/releasenotes/notes/umap-learn-denylist-b7c55f42f2408c24.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: add umap, numba and pynndescent to the Code Security denylist. From 739a8c7742b778117605935617cdaf80cc662cdd Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 18 Nov 2024 15:29:11 -0500 Subject: [PATCH 174/372] fix(lib-injection): ensure sitecustomize.py supports Python 2.7+ (#11381) --- .github/workflows/test_lib_injection.yml | 50 ++++++++++++ lib-injection/sources/sitecustomize.py | 77 +++++++++++++------ ...tion-version-support-ae2ff3a79ac1f6f2.yaml | 4 + 3 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/test_lib_injection.yml create mode 100644 releasenotes/notes/fix-lib-injection-version-support-ae2ff3a79ac1f6f2.yaml diff --git a/.github/workflows/test_lib_injection.yml b/.github/workflows/test_lib_injection.yml new file mode 100644 index 00000000000..7b9418390b8 --- /dev/null +++ b/.github/workflows/test_lib_injection.yml @@ -0,0 +1,50 @@ +name: Lib-injection tests + +on: + push: + branches: + - main + pull_request: + +jobs: + test_sitecustomize: + runs-on: ubuntu-latest + strategy: + matrix: + python: + # requires openssl 1.0, which is hard to get + # - "2.6" + # - "3.4" + # segfaults + # - 3.0" + # - 3.1" + # - "3.2" + # - "3.3" + - "2.7" + - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + steps: + - uses: actions/checkout@v4 + - name: Install pyenv + run: | + export PYENV_ROOT="${HOME}/.pyenv" + export PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}" + PYENV_GIT_TAG=main curl https://pyenv.run | bash + echo "PYENV_ROOT=${PYENV_ROOT}" >> $GITHUB_ENV + echo "PATH=${PATH}" >> $GITHUB_ENV + - name: Install python ${{ matrix.python }} + run: | + which pyenv + pyenv --version + pyenv install "${{ matrix.python }}" && pyenv global "${{ matrix.python }}" + - name: Print Python version + run: python --version + - name: Validate sitecustomize.py runs with ${{ matrix.python }} + run: python lib-injection/sources/sitecustomize.py diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 4ad07f4c60e..7e4eefb84f5 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -5,7 +5,6 @@ from collections import namedtuple import csv -import importlib.util import json import os import platform @@ -13,34 +12,42 @@ import subprocess import sys import time -from typing import Tuple Version = namedtuple("Version", ["version", "constraint"]) -def parse_version(version: str) -> Tuple: - constraint_idx = re.search(r"\d", version).start() - numeric = version[constraint_idx:] - constraint = version[:constraint_idx] - parsed_version = tuple(int(re.sub("[^0-9]", "", p)) for p in numeric.split(".")) - return Version(parsed_version, constraint) +def parse_version(version): + try: + constraint_match = re.search(r"\d", version) + if not constraint_match: + return Version((0, 0), "") + constraint_idx = constraint_match.start() + numeric = version[constraint_idx:] + constraint = version[:constraint_idx] + parsed_version = tuple(int(re.sub("[^0-9]", "", p)) for p in numeric.split(".")) + return Version(parsed_version, constraint) + except Exception: + return Version((0, 0), "") SCRIPT_DIR = os.path.dirname(__file__) RUNTIMES_ALLOW_LIST = { - "cpython": {"min": parse_version("3.7"), "max": parse_version("3.13")}, + "cpython": { + "min": Version(version=(3, 7), constraint=""), + "max": Version(version=(3, 13), constraint=""), + } } FORCE_INJECT = os.environ.get("DD_INJECT_FORCE", "").lower() in ("true", "1", "t") FORWARDER_EXECUTABLE = os.environ.get("DD_TELEMETRY_FORWARDER_PATH", "") TELEMETRY_ENABLED = "DD_INJECTION_ENABLED" in os.environ DEBUG_MODE = os.environ.get("DD_TRACE_DEBUG", "").lower() in ("true", "1", "t") -INSTALLED_PACKAGES = None -PYTHON_VERSION = None -PYTHON_RUNTIME = None -PKGS_ALLOW_LIST = None -EXECUTABLES_DENY_LIST = None +INSTALLED_PACKAGES = {} +PYTHON_VERSION = "unknown" +PYTHON_RUNTIME = "unknown" +PKGS_ALLOW_LIST = {} +EXECUTABLES_DENY_LIST = set() VERSION_COMPAT_FILE_LOCATIONS = ( os.path.abspath(os.path.join(SCRIPT_DIR, "../datadog-lib/min_compatible_versions.csv")), os.path.abspath(os.path.join(SCRIPT_DIR, "min_compatible_versions.csv")), @@ -103,7 +110,7 @@ def build_denied_executables(): cleaned = line.strip("\n") denied_executables.add(cleaned) denied_executables.add(os.path.basename(cleaned)) - _log(f"Built denied-executables list of {len(denied_executables)} entries", level="debug") + _log("Built denied-executables list of %s entries" % (len(denied_executables),), level="debug") return denied_executables @@ -143,9 +150,15 @@ def send_telemetry(event): stderr=subprocess.PIPE, universal_newlines=True, ) - p.stdin.write(event_json) - p.stdin.close() - _log("wrote telemetry to %s" % FORWARDER_EXECUTABLE, level="debug") + if p.stdin: + p.stdin.write(event_json) + p.stdin.close() + _log("wrote telemetry to %s" % FORWARDER_EXECUTABLE, level="debug") + else: + _log( + "failed to write telemetry to %s, could not write to telemetry writer stdin" % FORWARDER_EXECUTABLE, + level="error", + ) def _get_clib(): @@ -154,7 +167,7 @@ def _get_clib(): If GNU is not detected then returns MUSL. """ - libc, version = platform.libc_ver() + libc, _ = platform.libc_ver() if libc == "glibc": return "gnu" return "musl" @@ -170,7 +183,9 @@ def _log(msg, *args, **kwargs): if DEBUG_MODE: asctime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) msg = "[%s] [%s] datadog.autoinstrumentation(pid: %d): " % (asctime, level.upper(), os.getpid()) + msg % args - print(msg, file=sys.stderr) + sys.stderr.write(msg) + sys.stderr.write("\n") + sys.stderr.flush() def runtime_version_is_supported(python_runtime, python_version): @@ -197,13 +212,13 @@ def get_first_incompatible_sysarg(): _log("sys.argv not available, skipping sys.argv check", level="debug") return - _log(f"Checking sysargs: len(argv): {len(sys.argv)}", level="debug") + _log("Checking sys.args: len(sys.argv): %s" % (len(sys.argv),), level="debug") if len(sys.argv) <= 1: return argument = sys.argv[0] - _log(f"Is argument {argument} in deny-list?", level="debug") + _log("Is argument %s in deny-list?" % (argument,), level="debug") if argument in EXECUTABLES_DENY_LIST or os.path.basename(argument) in EXECUTABLES_DENY_LIST: - _log(f"argument {argument} is in deny-list", level="debug") + _log("argument %s is in deny-list" % (argument,), level="debug") return argument @@ -225,7 +240,13 @@ def _inject(): os.environ["_DD_INJECT_WAS_ATTEMPTED"] = "true" spec = None try: + # `find_spec` is only available in Python 3.4+ + # https://docs.python.org/3/library/importlib.html#importlib.util.find_spec + # DEV: It is ok to fail here on import since it'll only fail on Python versions we don't support / inject into + import importlib.util + # None is a valid return value for find_spec (module was not found), so we need to check for it explicitly + spec = importlib.util.find_spec("ddtrace") if not spec: raise ModuleNotFoundError("ddtrace") @@ -377,5 +398,11 @@ def _inject(): try: _inject() -except Exception: - pass # absolutely never allow exceptions to propagate to the app +except Exception as e: + try: + event = gen_telemetry_payload( + [create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])] + ) + send_telemetry(event) + except Exception: + pass # absolutely never allow exceptions to propagate to the app diff --git a/releasenotes/notes/fix-lib-injection-version-support-ae2ff3a79ac1f6f2.yaml b/releasenotes/notes/fix-lib-injection-version-support-ae2ff3a79ac1f6f2.yaml new file mode 100644 index 00000000000..3978ccd89f4 --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-version-support-ae2ff3a79ac1f6f2.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: Support Python 2.7+ for injection compatibility check. From 79483a5459d228f38df60a22619dff0d2e304cf8 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 18 Nov 2024 15:38:22 -0500 Subject: [PATCH 175/372] ci: set ulimit -c unlimited for gitlab tests (#11400) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/testrunner.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/testrunner.yml b/.gitlab/testrunner.yml index 38e2111ec78..bdee810249a 100644 --- a/.gitlab/testrunner.yml +++ b/.gitlab/testrunner.yml @@ -4,5 +4,6 @@ tags: [ "arch:amd64" ] timeout: 20m before_script: + - ulimit -c unlimited - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ From 5f7d6a07aeaa34d38a7a7c26fa401368985cb80a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 20:43:21 +0000 Subject: [PATCH 176/372] chore: update starlette latest version to 0.41.2 (#11354) Update starlette lockfiles and dependency package lockfiles. This performs the following updates: 1) Some starlette lockfiles use starlette `latest`. This will update starlette and dependencies. 2) Some starlette lockfiles use a pinned (non-latest) version of starlette, but require a `latest` dependency version. This will update the dependencies. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/112dc22.txt | 6 +++--- .riot/requirements/1250d61.txt | 2 +- .riot/requirements/156e3cc.txt | 10 +++++----- .riot/requirements/17a0f7f.txt | 10 +++++----- .riot/requirements/18392ae.txt | 14 +++++++------- .riot/requirements/1b846e9.txt | 6 +++--- .riot/requirements/1c489e9.txt | 2 +- .riot/requirements/2c855a9.txt | 14 +++++++------- .riot/requirements/41529f2.txt | 12 ++++++------ .riot/requirements/4b1629e.txt | 12 ++++++------ .riot/requirements/7b8e50e.txt | 6 +++--- .riot/requirements/7f7863d.txt | 14 +++++++------- .riot/requirements/7ff8c97.txt | 12 ++++++------ .riot/requirements/91a3315.txt | 2 +- .riot/requirements/b06b6cb.txt | 12 ++++++------ .riot/requirements/c52f9f6.txt | 8 ++++---- .riot/requirements/d6ceb22.txt | 12 ++++++------ .riot/requirements/d88b3ac.txt | 14 +++++++------- .riot/requirements/ec338d4.txt | 16 ++++++++-------- 19 files changed, 92 insertions(+), 92 deletions(-) diff --git a/.riot/requirements/112dc22.txt b/.riot/requirements/112dc22.txt index 5a7a17b01c7..66f398229a6 100644 --- a/.riot/requirements/112dc22.txt +++ b/.riot/requirements/112dc22.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.14.2 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/1250d61.txt b/.riot/requirements/1250d61.txt index 90fc52cde99..2fc3bd39576 100644 --- a/.riot/requirements/1250d61.txt +++ b/.riot/requirements/1250d61.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/156e3cc.txt b/.riot/requirements/156e3cc.txt index 1525da6c414..0ab8b16d0de 100644 --- a/.riot/requirements/156e3cc.txt +++ b/.riot/requirements/156e3cc.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,13 +21,13 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/17a0f7f.txt b/.riot/requirements/17a0f7f.txt index d6725f817cd..b4b0a7c9503 100644 --- a/.riot/requirements/17a0f7f.txt +++ b/.riot/requirements/17a0f7f.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,13 +21,13 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/18392ae.txt b/.riot/requirements/18392ae.txt index 933de189d61..fb77230f5a3 100644 --- a/.riot/requirements/18392ae.txt +++ b/.riot/requirements/18392ae.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.39.2 -tomli==2.0.2 +starlette==0.41.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1b846e9.txt b/.riot/requirements/1b846e9.txt index 4d174ec147c..378008e2448 100644 --- a/.riot/requirements/1b846e9.txt +++ b/.riot/requirements/1b846e9.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/1c489e9.txt b/.riot/requirements/1c489e9.txt index 92254158db9..01e263f1e3a 100644 --- a/.riot/requirements/1c489e9.txt +++ b/.riot/requirements/1c489e9.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/2c855a9.txt b/.riot/requirements/2c855a9.txt index 0145630e743..33e8f8f4af3 100644 --- a/.riot/requirements/2c855a9.txt +++ b/.riot/requirements/2c855a9.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.14.2 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/41529f2.txt b/.riot/requirements/41529f2.txt index 83a605e2881..13d7043047a 100644 --- a/.riot/requirements/41529f2.txt +++ b/.riot/requirements/41529f2.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,17 +21,17 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.39.2 +starlette==0.41.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/4b1629e.txt b/.riot/requirements/4b1629e.txt index 76be9c2a6d2..b5b3335484e 100644 --- a/.riot/requirements/4b1629e.txt +++ b/.riot/requirements/4b1629e.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/7b8e50e.txt b/.riot/requirements/7b8e50e.txt index cd7fc9062b9..310164f1e0d 100644 --- a/.riot/requirements/7b8e50e.txt +++ b/.riot/requirements/7b8e50e.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/7f7863d.txt b/.riot/requirements/7f7863d.txt index 361a3483306..598f660a429 100644 --- a/.riot/requirements/7f7863d.txt +++ b/.riot/requirements/7f7863d.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/7ff8c97.txt b/.riot/requirements/7ff8c97.txt index ab3925fdd61..1ffe5129825 100644 --- a/.riot/requirements/7ff8c97.txt +++ b/.riot/requirements/7ff8c97.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/91a3315.txt b/.riot/requirements/91a3315.txt index 59c24169550..2f05255dc5d 100644 --- a/.riot/requirements/91a3315.txt +++ b/.riot/requirements/91a3315.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/b06b6cb.txt b/.riot/requirements/b06b6cb.txt index f74a362923f..e277fb20f5a 100644 --- a/.riot/requirements/b06b6cb.txt +++ b/.riot/requirements/b06b6cb.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.15.0 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/c52f9f6.txt b/.riot/requirements/c52f9f6.txt index c65ef57f787..6b62e916274 100644 --- a/.riot/requirements/c52f9f6.txt +++ b/.riot/requirements/c52f9f6.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -34,8 +34,8 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.39.2 -tomli==2.0.2 +starlette==0.41.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/d6ceb22.txt b/.riot/requirements/d6ceb22.txt index 0749513724c..6ee8e73017d 100644 --- a/.riot/requirements/d6ceb22.txt +++ b/.riot/requirements/d6ceb22.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,17 +21,17 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.39.2 +starlette==0.41.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/d88b3ac.txt b/.riot/requirements/d88b3ac.txt index b1cbde620f2..785f5b75b11 100644 --- a/.riot/requirements/d88b3ac.txt +++ b/.riot/requirements/d88b3ac.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.0.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/ec338d4.txt b/.riot/requirements/ec338d4.txt index e5a0a713b20..2c823fe1300 100644 --- a/.riot/requirements/ec338d4.txt +++ b/.riot/requirements/ec338d4.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.4 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.39.2 -tomli==2.0.2 +starlette==0.41.2 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 From ada539b45ec91c80f9292df52f871cbd4a9661d3 Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Tue, 19 Nov 2024 16:08:31 +0200 Subject: [PATCH 177/372] chore: update Docker Hub reference (#11187) Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- docs/contributing-integrations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing-integrations.rst b/docs/contributing-integrations.rst index baaf5d9aa7d..e7a2899b175 100644 --- a/docs/contributing-integrations.rst +++ b/docs/contributing-integrations.rst @@ -140,7 +140,7 @@ They use the Flask integration tests as a teaching example. Referencing these in server is started by a Pytest fixture function that's defined in the snapshot test file. 6. If the library you're integrating with requires communication with a datastore, make sure there's an image for that datastore referenced in ``docker-compose.yml``. If there is not, add one. - You can find a suitable image by searching on `Dockerhub `_. + You can find a suitable image by searching on `Docker Hub `_. 7. Write a simple test. In your new snapshot test file, define a function testing your app's happy path. Here's an example from the Flask test suite: From 7e7aafb26502633600b44ea993df8fb7bb2fa666 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:24:57 +0000 Subject: [PATCH 178/372] chore(ci_visibility): don't incorrectly report tests as new (#11442) This tweaks the logic for marking tests as "new" (with the `test.is_new` tag) to not mark tests as new: - if fetching unique tests failed (treated as "the length of unique tests is 0") - if the test has parameters (this check is done twice since we allow `pytest` to set parameters after a test has been discovered) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/api/_test.py | 2 +- ddtrace/internal/ci_visibility/recorder.py | 12 +- ...ers.test_manual_api_fake_efd_all_pass.json | 936 +++++++++-------- ...st_manual_api_fake_efd_faulty_session.json | 574 ++++++----- ...ers.test_manual_api_fake_efd_mix_fail.json | 938 +++++++++-------- ...ers.test_manual_api_fake_efd_mix_pass.json | 962 +++++++++--------- 6 files changed, 1712 insertions(+), 1712 deletions(-) diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index a0d7a6e0fa9..dab6d3e0be6 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -101,7 +101,7 @@ def _set_efd_tags(self) -> None: # NOTE: The is_new tag is currently only being set in the context of EFD (since that is the only context in # which unique tests are fetched). - if self._is_new: + if self.is_new(): self.set_tag(TEST_IS_NEW, self._is_new) def _set_atr_tags(self) -> None: diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 8d1922b8663..d7f6986566c 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -868,6 +868,12 @@ def is_unique_test(cls, test_id: Union[TestId, InternalTestId]) -> bool: if instance is None: return False + # The assumption that we were not able to fetch unique tests properly if the length is 0 is acceptable + # because the current EFD usage would cause the session to be faulty even if the query was successful but + # not unique tests exist. In this case, we assume all tests are unique. + if len(instance._unique_test_ids) == 0: + return True + return test_id in instance._unique_test_ids @@ -1106,8 +1112,10 @@ def _on_discover_test(discover_args: Test.DiscoverArgs): log.debug("Handling discovery for test %s", discover_args.test_id) suite = CIVisibility.get_suite_by_id(discover_args.test_id.parent_id) - # New tests are currently only considered for EFD - if CIVisibility.is_efd_enabled(): + # New tests are currently only considered for EFD: + # - if known tests were fetched properly (enforced by is_unique_test) + # - if they have no parameters + if CIVisibility.is_efd_enabled() and discover_args.test_id.parameters is None: is_new = not CIVisibility.is_unique_test(discover_args.test_id) else: is_new = False diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json index 566f49d6e36..2ed126360da 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,28 +35,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -64,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 75083, - "start": 1727948917331802425 + "duration": 1234661209, + "start": 1732017031398084177 }], [ { @@ -82,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -97,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -108,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -127,9 +126,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -137,12 +136,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 82625, - "start": 1727948917347338050 + "duration": 84417, + "start": 1732017032647964594 }], [ { @@ -155,11 +154,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -170,7 +169,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -181,13 +180,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -200,9 +199,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -210,12 +209,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 51208, - "start": 1727948917347529092 + "duration": 50292, + "start": 1732017032648149219 }], [ { @@ -228,11 +227,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -243,7 +242,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -254,13 +253,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -273,9 +272,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -283,12 +282,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 59917, - "start": 1727948917347659383 + "duration": 45583, + "start": 1732017032648277594 }], [ { @@ -301,11 +300,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -316,7 +315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -327,13 +326,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -346,9 +345,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -356,12 +355,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47292, - "start": 1727948917347798133 + "duration": 42500, + "start": 1732017032648394761 }], [ { @@ -374,11 +373,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -389,7 +388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -400,13 +399,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -419,9 +418,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -429,12 +428,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45291, - "start": 1727948917347919467 + "duration": 42708, + "start": 1732017032648507386 }], [ { @@ -447,11 +446,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -462,7 +461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -473,13 +472,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -492,9 +491,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -502,12 +501,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45625, - "start": 1727948917348038175 + "duration": 42541, + "start": 1732017032648618636 }], [ { @@ -520,11 +519,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -535,7 +534,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -546,13 +545,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -565,9 +564,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -575,12 +574,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46417, - "start": 1727948917348159508 + "duration": 43209, + "start": 1732017032648732052 }], [ { @@ -593,11 +592,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -608,7 +607,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -619,13 +618,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -638,9 +637,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -648,12 +647,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43542, - "start": 1727948917348279675 + "duration": 56875, + "start": 1732017032648847719 }], [ { @@ -666,11 +665,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -681,7 +680,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -692,13 +691,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -711,9 +710,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -721,12 +720,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43709, - "start": 1727948917348396133 + "duration": 66792, + "start": 1732017032648980219 }], [ { @@ -739,11 +738,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -754,7 +753,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -765,27 +764,28 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -793,12 +793,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234559896, - "start": 1727948917331708008 + "duration": 64542, + "start": 1732017032649171969 }], [ { @@ -811,11 +811,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,27 +837,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -865,10 +864,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 45917, - "start": 1727948917348646633 + "duration": 6543266750, + "start": 1732017026106176886 }], [ { @@ -881,11 +880,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -896,7 +895,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -907,13 +906,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -925,9 +924,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -935,10 +934,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 32250, - "start": 1727948917348769592 + "duration": 38417, + "start": 1732017032649531219 }], [ { @@ -951,11 +950,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -966,7 +965,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -977,13 +976,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -995,9 +994,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1005,10 +1004,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 33041, - "start": 1727948917348873092 + "duration": 37292, + "start": 1732017032649645802 }], [ { @@ -1021,11 +1020,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1036,7 +1035,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1047,13 +1046,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1065,9 +1064,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1075,10 +1074,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 31750, - "start": 1727948917348978717 + "duration": 48833, + "start": 1732017032649827761 }], [ { @@ -1091,11 +1090,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1106,7 +1105,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1117,13 +1116,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1135,9 +1134,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1145,10 +1144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 32125, - "start": 1727948917349080925 + "duration": 37208, + "start": 1732017032649957136 }], [ { @@ -1161,11 +1160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1176,7 +1175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1187,26 +1186,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1214,10 +1214,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 6543210081, - "start": 1727948917348599967 + "duration": 33208, + "start": 1732017032650066219 }], [ { @@ -1230,11 +1230,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,13 +1256,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_efd_all_pass", @@ -1274,9 +1274,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1284,12 +1284,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 12, "test.source.start": 4 }, - "duration": 71125, - "start": 1727948917349744550 + "duration": 115459, + "start": 1732017032650352052 }], [ { @@ -1302,11 +1302,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,27 +1328,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p1", "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1356,10 +1355,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 41042, - "start": 1727948917349878550 + "duration": 43042, + "start": 1732017032650542802 }], [ { @@ -1372,11 +1371,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1387,7 +1386,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1398,27 +1397,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test" }, "metrics": { @@ -1426,10 +1424,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 38917, - "start": 1727948917349980175 + "duration": 41291, + "start": 1732017032650651386 }], [ { @@ -1442,11 +1440,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1457,7 +1455,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1468,13 +1466,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_all_pass", @@ -1483,7 +1481,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "14690757972509276569", + "test_session_id": "10359560244968602395", "type": "test_session_end" }, "metrics": { @@ -1491,10 +1489,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 20695250, - "start": 1727948917331276050 + "duration": 20544333, + "start": 1732017032632107886 }, { "name": "test_visibility.module", @@ -1506,11 +1504,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1521,7 +1519,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1532,12 +1530,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_all_pass", @@ -1547,8 +1545,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", "type": "test_module_end" }, "metrics": { @@ -1556,8 +1554,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18545334, - "start": 1727948917331637383 + "duration": 18323375, + "start": 1732017032632584552 }, { "name": "test_visibility.suite", @@ -1569,11 +1567,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1584,7 +1582,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1595,12 +1593,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1609,9 +1607,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13890618494655922319", - "test_session_id": "14690757972509276569", - "test_suite_id": "12093624018441448955", + "test_module_id": "5725003850113608623", + "test_session_id": "10359560244968602395", + "test_suite_id": "5168108260619620972", "type": "test_suite_end" }, "metrics": { @@ -1619,8 +1617,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18438708, - "start": 1727948917331663592 + "duration": 18177084, + "start": 1732017032632614302 }, { "name": "test_visibility.module", @@ -1632,11 +1630,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1647,7 +1645,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1658,12 +1656,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_all_pass", @@ -1673,8 +1671,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", "type": "test_module_end" }, "metrics": { @@ -1682,8 +1680,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1651625, - "start": 1727948917350234217 + "duration": 1590458, + "start": 1732017032650960761 }, { "name": "test_visibility.suite", @@ -1695,11 +1693,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1710,7 +1708,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1721,12 +1719,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1735,9 +1733,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test_suite_end" }, "metrics": { @@ -1745,8 +1743,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 917625, - "start": 1727948917350256300 + "duration": 885792, + "start": 1732017032650986594 }, { "name": "test_visibility.suite", @@ -1758,11 +1756,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1773,7 +1771,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1784,12 +1782,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1798,9 +1796,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test_suite_end" }, "metrics": { @@ -1808,8 +1806,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 590458, - "start": 1727948917351224050 + "duration": 545458, + "start": 1732017032651922719 }], [ { @@ -1822,11 +1820,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1837,7 +1835,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1848,13 +1846,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1864,9 +1862,9 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -1874,10 +1872,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 37208, - "start": 1727948917350275300 + "duration": 39125, + "start": 1732017032651006219 }], [ { @@ -1890,11 +1888,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1905,7 +1903,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1916,13 +1914,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -1932,9 +1930,9 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -1942,10 +1940,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 36625, - "start": 1727948917350374800 + "duration": 36333, + "start": 1732017032651104219 }], [ { @@ -1958,11 +1956,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1973,7 +1971,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1984,13 +1982,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2000,9 +1998,9 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2010,10 +2008,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 38000, - "start": 1727948917350472383 + "duration": 35375, + "start": 1732017032651196552 }], [ { @@ -2026,11 +2024,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2041,7 +2039,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2052,13 +2050,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2068,9 +2066,9 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2078,10 +2076,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 39083, - "start": 1727948917350572592 + "duration": 35000, + "start": 1732017032651288386 }], [ { @@ -2094,11 +2092,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2109,7 +2107,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2120,13 +2118,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2136,9 +2134,9 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2146,10 +2144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 40875, - "start": 1727948917350683675 + "duration": 36292, + "start": 1732017032651380927 }], [ { @@ -2162,11 +2160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2177,7 +2175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2188,13 +2186,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2204,9 +2202,9 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2214,10 +2212,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 34334, - "start": 1727948917350785383 + "duration": 36583, + "start": 1732017032651485636 }], [ { @@ -2230,11 +2228,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2245,7 +2243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2256,13 +2254,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2272,9 +2270,9 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2282,10 +2280,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, "duration": 35375, - "start": 1727948917350877383 + "start": 1732017032651576386 }], [ { @@ -2298,11 +2296,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2313,7 +2311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2324,13 +2322,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2340,9 +2338,9 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2350,10 +2348,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 36167, - "start": 1727948917350970258 + "duration": 34542, + "start": 1732017032651667552 }], [ { @@ -2366,11 +2364,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2381,7 +2379,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2392,13 +2390,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2408,9 +2406,9 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "10312966550546275122", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "16321082474709595335", "type": "test" }, "metrics": { @@ -2418,10 +2416,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 34791, - "start": 1727948917351063842 + "duration": 33708, + "start": 1732017032651755719 }], [ { @@ -2434,11 +2432,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2449,7 +2447,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2460,13 +2458,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2477,9 +2475,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test" }, "metrics": { @@ -2487,12 +2485,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47500, - "start": 1727948917351243508 + "duration": 52333, + "start": 1732017032651943344 }], [ { @@ -2505,11 +2503,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2520,7 +2518,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2531,13 +2529,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.early_flake.abort_reason": "slow", @@ -2549,9 +2547,9 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test" }, "metrics": { @@ -2559,10 +2557,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 54667, - "start": 1727948917351350925 + "duration": 301000042125, + "start": 1732016731652053344 }], [ { @@ -2575,11 +2573,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2590,7 +2588,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2601,13 +2599,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_efd_all_pass", @@ -2620,9 +2618,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test" }, "metrics": { @@ -2630,12 +2628,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225, + "process_id": 38724, "test.source.end": 12, "test.source.start": 4 }, - "duration": 55667, - "start": 1727948917351465883 + "duration": 54833, + "start": 1732017032652158344 }], [ { @@ -2648,11 +2646,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2663,7 +2661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2674,13 +2672,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2690,9 +2688,9 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test" }, "metrics": { @@ -2700,10 +2698,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 34833, - "start": 1727948917351594092 + "duration": 34542, + "start": 1732017032652269802 }], [ { @@ -2716,11 +2714,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe687500000000", + "_dd.p.tid": "673c7b8800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2731,7 +2729,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-107/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2742,13 +2740,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "303672003d7448c5a6d0bf5146498f47", + "runtime-id": "0728e603bc8846889415595c15019eb7", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_all_pass", "test.framework": "dd_manual_test_fw", @@ -2758,9 +2756,9 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7904986711440793627", - "test_session_id": "14690757972509276569", - "test_suite_id": "11891008386569702515", + "test_module_id": "2177262413998606555", + "test_session_id": "10359560244968602395", + "test_suite_id": "9627260334977856774", "type": "test" }, "metrics": { @@ -2768,8 +2766,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 98225 + "process_id": 38724 }, - "duration": 36750, - "start": 1727948917351704175 + "duration": 34958, + "start": 1732017032652360261 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json index a3cb66658d9..d928f7b2340 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -53,9 +53,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test" }, "metrics": { @@ -63,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680, + "process_id": 38755, "test.source.end": 2, "test.source.start": 1 }, - "duration": 130584, - "start": 1727941009974023542 + "duration": 94917, + "start": 1732017042085932542 }], [ { @@ -81,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -96,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -107,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -124,9 +124,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test" }, "metrics": { @@ -134,10 +134,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 75125, - "start": 1727941009988450751 + "duration": 95916, + "start": 1732017042106447751 }], [ { @@ -150,11 +150,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -165,7 +165,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -176,13 +176,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_efd_faulty_session", @@ -194,9 +194,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test" }, "metrics": { @@ -204,12 +204,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680, + "process_id": 38755, "test.source.end": 12, "test.source.start": 4 }, - "duration": 91042, - "start": 1727941009988618042 + "duration": 77750, + "start": 1732017042106673417 }], [ { @@ -222,11 +222,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -237,7 +237,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -248,27 +248,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p1", "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test" }, "metrics": { @@ -276,10 +275,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 39250, - "start": 1727941009988778001 + "duration": 46209, + "start": 1732017042106826542 }], [ { @@ -292,11 +291,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -307,7 +306,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -318,27 +317,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test" }, "metrics": { @@ -346,10 +344,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 36459, - "start": 1727941009988878042 + "duration": 37125, + "start": 1732017042106954959 }], [ { @@ -362,11 +360,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -377,7 +375,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -388,13 +386,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -404,7 +402,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "18216439274108096544", + "test_session_id": "13536905200439174622", "type": "test_session_end" }, "metrics": { @@ -412,10 +410,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 17266542, - "start": 1727941009973558417 + "duration": 23710042, + "start": 1732017042085392042 }, { "name": "test_visibility.module", @@ -427,11 +425,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -442,7 +440,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -453,12 +451,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -468,8 +466,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", "type": "test_module_end" }, "metrics": { @@ -477,8 +475,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 15110084, - "start": 1727941009973967292 + "duration": 21324541, + "start": 1732017042085874126 }, { "name": "test_visibility.suite", @@ -490,11 +488,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -505,7 +503,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -516,12 +514,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -530,9 +528,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "11771646327533647561", - "test_session_id": "18216439274108096544", - "test_suite_id": "13127216409851761801", + "test_module_id": "6680100430092765332", + "test_session_id": "13536905200439174622", + "test_suite_id": "921439586437549873", "type": "test_suite_end" }, "metrics": { @@ -540,8 +538,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 15004625, - "start": 1727941009973993584 + "duration": 21202958, + "start": 1732017042085901959 }, { "name": "test_visibility.module", @@ -553,11 +551,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -568,7 +566,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -579,12 +577,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -594,8 +592,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", "type": "test_module_end" }, "metrics": { @@ -603,8 +601,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1606750, - "start": 1727941009989129292 + "duration": 1728375, + "start": 1732017042107254584 }, { "name": "test_visibility.suite", @@ -616,11 +614,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -631,7 +629,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -642,12 +640,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -656,9 +654,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test_suite_end" }, "metrics": { @@ -666,8 +664,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 906166, - "start": 1727941009989151626 + "duration": 932500, + "start": 1732017042107278876 }, { "name": "test_visibility.suite", @@ -679,11 +677,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -694,7 +692,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -705,12 +703,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -719,9 +717,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test_suite_end" }, "metrics": { @@ -729,8 +727,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 544375, - "start": 1727941009990111209 + "duration": 636416, + "start": 1732017042108264001 }], [ { @@ -743,11 +741,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -758,7 +756,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -769,13 +767,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -786,9 +784,9 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -796,10 +794,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 38125, - "start": 1727941009989170917 + "duration": 34542, + "start": 1732017042107299417 }], [ { @@ -812,11 +810,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -827,7 +825,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -838,13 +836,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -855,9 +853,9 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -865,10 +863,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 36708, - "start": 1727941009989270709 + "duration": 32166, + "start": 1732017042107404001 }], [ { @@ -881,11 +879,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -896,7 +894,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -907,13 +905,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -924,9 +922,9 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -934,10 +932,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 34917, - "start": 1727941009989368834 + "duration": 31708, + "start": 1732017042107500376 }], [ { @@ -950,11 +948,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -965,7 +963,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -976,13 +974,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -993,9 +991,9 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1003,10 +1001,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 36584, - "start": 1727941009989461292 + "duration": 32375, + "start": 1732017042107595626 }], [ { @@ -1019,11 +1017,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1034,7 +1032,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1045,13 +1043,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1062,9 +1060,9 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1072,10 +1070,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 34584, - "start": 1727941009989554167 + "duration": 34958, + "start": 1732017042107707209 }], [ { @@ -1088,11 +1086,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1103,7 +1101,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1114,13 +1112,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1131,9 +1129,9 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1141,10 +1139,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 45875, - "start": 1727941009989647751 + "duration": 30750, + "start": 1732017042107806834 }], [ { @@ -1157,11 +1155,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1172,7 +1170,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1183,13 +1181,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1200,9 +1198,9 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1210,10 +1208,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 35166, - "start": 1727941009989754876 + "duration": 31041, + "start": 1732017042107900501 }], [ { @@ -1226,11 +1224,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1241,7 +1239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1252,13 +1250,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1269,9 +1267,9 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1279,10 +1277,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 36167, - "start": 1727941009989850292 + "duration": 31167, + "start": 1732017042107992792 }], [ { @@ -1295,11 +1293,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1310,7 +1308,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1321,13 +1319,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1338,9 +1336,9 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "1778612195395614986", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "436066589762063296", "type": "test" }, "metrics": { @@ -1348,10 +1346,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 34500, - "start": 1727941009989946167 + "duration": 30625, + "start": 1732017042108095459 }], [ { @@ -1364,11 +1362,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1379,7 +1377,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1390,13 +1388,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1407,9 +1405,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test" }, "metrics": { @@ -1417,12 +1415,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680, + "process_id": 38755, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48417, - "start": 1727941009990131209 + "duration": 44375, + "start": 1732017042108283959 }], [ { @@ -1435,11 +1433,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1450,7 +1448,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1461,13 +1459,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1478,9 +1476,9 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test" }, "metrics": { @@ -1488,10 +1486,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 40625, - "start": 1727941009990239459 + "duration": 33083, + "start": 1732017042108396584 }], [ { @@ -1504,11 +1502,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1519,7 +1517,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1530,13 +1528,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_efd_faulty_session", @@ -1549,9 +1547,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test" }, "metrics": { @@ -1559,12 +1557,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680, + "process_id": 38755, "test.source.end": 12, "test.source.start": 4 }, - "duration": 52000, - "start": 1727941009990339334 + "duration": 52750, + "start": 1732017042108494626 }], [ { @@ -1577,11 +1575,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1592,7 +1590,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1603,13 +1601,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1620,9 +1618,9 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test" }, "metrics": { @@ -1630,10 +1628,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 36292, - "start": 1727941009990454584 + "duration": 35375, + "start": 1732017042108617667 }], [ { @@ -1646,11 +1644,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe499100000000", + "_dd.p.tid": "673c7b9200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1661,7 +1659,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-88/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1672,13 +1670,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "6a7a616d2c94479eac2502e0ba9e6c86", + "runtime-id": "e1319046da864b348691a08852b812aa", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1688,9 +1686,9 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2159240970471013460", - "test_session_id": "18216439274108096544", - "test_suite_id": "76542733298697855", + "test_module_id": "9158184750463906951", + "test_session_id": "13536905200439174622", + "test_suite_id": "12430754391017424803", "type": "test" }, "metrics": { @@ -1698,8 +1696,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 78680 + "process_id": 38755 }, - "duration": 33667, - "start": 1727941009990550959 + "duration": 31833, + "start": 1732017042108784959 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json index 9ec2386f182..f6c8b2cbd50 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,28 +35,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -64,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 69250, - "start": 1727952453914758465 + "duration": 1234653625, + "start": 1732017045301859670 }], [ { @@ -82,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -97,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -108,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -127,9 +126,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -137,12 +136,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 71667, - "start": 1727952453929447381 + "duration": 96708, + "start": 1732017046552997878 }], [ { @@ -155,11 +154,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -170,7 +169,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -181,13 +180,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -200,9 +199,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -210,12 +209,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 49542, - "start": 1727952453929617756 + "duration": 52875, + "start": 1732017046553204003 }], [ { @@ -228,11 +227,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -243,7 +242,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -254,13 +253,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -273,9 +272,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -283,12 +282,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48334, - "start": 1727952453929748006 + "duration": 47125, + "start": 1732017046553335503 }], [ { @@ -301,11 +300,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -316,7 +315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -327,13 +326,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -346,9 +345,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -356,12 +355,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44083, - "start": 1727952453929873215 + "duration": 48583, + "start": 1732017046553474503 }], [ { @@ -374,11 +373,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -389,7 +388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -400,13 +399,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -419,9 +418,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -429,12 +428,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46500, - "start": 1727952453930006381 + "duration": 42750, + "start": 1732017046553594545 }], [ { @@ -447,11 +446,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -462,7 +461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -473,13 +472,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -492,9 +491,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -502,12 +501,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44000, - "start": 1727952453930128090 + "duration": 48375, + "start": 1732017046553735753 }], [ { @@ -520,11 +519,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -535,7 +534,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -546,13 +545,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -565,9 +564,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -575,12 +574,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43042, - "start": 1727952453930244506 + "duration": 43667, + "start": 1732017046553858211 }], [ { @@ -593,11 +592,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -608,7 +607,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -619,13 +618,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -638,9 +637,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -648,12 +647,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 41375, - "start": 1727952453930358631 + "duration": 42958, + "start": 1732017046553970920 }], [ { @@ -666,11 +665,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -681,7 +680,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -692,13 +691,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -711,9 +710,9 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -721,12 +720,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44958, - "start": 1727952453930473673 + "duration": 45750, + "start": 1732017046554098211 }], [ { @@ -739,11 +738,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -754,7 +753,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -765,27 +764,28 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -793,12 +793,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234560026, - "start": 1727952453914668006 + "duration": 41917, + "start": 1732017046554212711 }], [ { @@ -811,11 +811,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,27 +837,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -865,10 +864,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 33042, - "start": 1727952453930728798 + "duration": 6543248625, + "start": 1732017040011126045 }], [ { @@ -881,11 +880,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -896,7 +895,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -907,13 +906,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -925,9 +924,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -935,10 +934,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 32875, - "start": 1727952453930835590 + "duration": 32792, + "start": 1732017046554443461 }], [ { @@ -951,11 +950,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -966,7 +965,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -977,13 +976,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -995,9 +994,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1005,10 +1004,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 32792, - "start": 1727952453930948256 + "duration": 80333, + "start": 1732017046554544878 }], [ { @@ -1021,11 +1020,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1036,7 +1035,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1047,13 +1046,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1065,9 +1064,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1075,10 +1074,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 32042, - "start": 1727952453931054923 + "duration": 35167, + "start": 1732017046554721336 }], [ { @@ -1091,11 +1090,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1106,7 +1105,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1117,13 +1116,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1135,9 +1134,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1145,10 +1144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 32209, - "start": 1727952453931162881 + "duration": 32625, + "start": 1732017046554827295 }], [ { @@ -1161,11 +1160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1176,7 +1175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1187,26 +1186,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1214,10 +1214,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 6543209894, - "start": 1727952453930684506 + "duration": 32334, + "start": 1732017046554926836 }], [ { @@ -1230,11 +1230,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,13 +1256,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_efd_mix_fail", @@ -1274,9 +1274,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1284,12 +1284,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 12, "test.source.start": 4 }, - "duration": 68416, - "start": 1727952453931782590 + "duration": 86583, + "start": 1732017046555034003 }], [ { @@ -1302,11 +1302,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,27 +1328,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p1", "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1356,10 +1355,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 53208, - "start": 1727952453931916423 + "duration": 39250, + "start": 1732017046555184920 }], [ { @@ -1372,11 +1371,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1387,7 +1386,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1398,27 +1397,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test" }, "metrics": { @@ -1426,10 +1424,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 38125, - "start": 1727952453932033423 + "duration": 36583, + "start": 1732017046555281670 }], [ { @@ -1442,11 +1440,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1457,7 +1455,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1468,13 +1466,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_fail", @@ -1483,7 +1481,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "2843325375140995787", + "test_session_id": "8540679216798029288", "type": "test_session_end" }, "metrics": { @@ -1491,10 +1489,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 19749750, - "start": 1727952453914268840 + "duration": 21474500, + "start": 1732017046535832128 }, { "name": "test_visibility.module", @@ -1506,11 +1504,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1521,7 +1519,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1532,12 +1530,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_fail", @@ -1547,8 +1545,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "fail", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", "type": "test_module_end" }, "metrics": { @@ -1556,8 +1554,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 17620333, - "start": 1727952453914614048 + "duration": 19144958, + "start": 1732017046536355503 }, { "name": "test_visibility.suite", @@ -1569,11 +1567,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1584,7 +1582,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1595,12 +1593,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1609,9 +1607,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15829573390530922189", - "test_session_id": "2843325375140995787", - "test_suite_id": "11480570247312318808", + "test_module_id": "15225714558851568795", + "test_session_id": "8540679216798029288", + "test_suite_id": "2519961191575781039", "type": "test_suite_end" }, "metrics": { @@ -1619,8 +1617,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 17514708, - "start": 1727952453914638423 + "duration": 19024250, + "start": 1732017046536387836 }, { "name": "test_visibility.module", @@ -1632,11 +1630,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1647,7 +1645,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1658,12 +1656,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_fail", @@ -1673,8 +1671,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", "type": "test_module_end" }, "metrics": { @@ -1682,8 +1680,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1636792, - "start": 1727952453932284881 + "duration": 1650917, + "start": 1732017046555550253 }, { "name": "test_visibility.suite", @@ -1695,11 +1693,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1710,7 +1708,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1721,12 +1719,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1735,9 +1733,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test_suite_end" }, "metrics": { @@ -1745,8 +1743,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 923125, - "start": 1727952453932307131 + "duration": 914292, + "start": 1732017046555574086 }, { "name": "test_visibility.suite", @@ -1758,11 +1756,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1773,7 +1771,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1784,12 +1782,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1798,9 +1796,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test_suite_end" }, "metrics": { @@ -1808,8 +1806,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 568167, - "start": 1727952453933280673 + "duration": 579250, + "start": 1732017046556536836 }], [ { @@ -1822,11 +1820,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1837,7 +1835,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1848,13 +1846,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1864,9 +1862,9 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -1874,10 +1872,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 37584, - "start": 1727952453932328256 + "duration": 37875, + "start": 1732017046555593045 }], [ { @@ -1890,11 +1888,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1905,7 +1903,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1916,13 +1914,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1932,9 +1930,9 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -1942,10 +1940,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 36625, - "start": 1727952453932426465 + "duration": 34916, + "start": 1732017046555688295 }], [ { @@ -1958,11 +1956,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1973,7 +1971,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1984,13 +1982,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2000,9 +1998,9 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2010,10 +2008,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 35750, - "start": 1727952453932524006 + "duration": 37625, + "start": 1732017046555779711 }], [ { @@ -2026,11 +2024,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2041,7 +2039,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2052,13 +2050,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2068,9 +2066,9 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2078,10 +2076,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 38042, - "start": 1727952453932622131 + "duration": 34792, + "start": 1732017046555873378 }], [ { @@ -2094,11 +2092,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2109,7 +2107,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2120,13 +2118,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2136,9 +2134,9 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2146,10 +2144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 36667, - "start": 1727952453932725006 + "duration": 36041, + "start": 1732017046555966420 }], [ { @@ -2162,11 +2160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2177,7 +2175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2188,13 +2186,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2204,9 +2202,9 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2214,10 +2212,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 33958, - "start": 1727952453932827840 + "duration": 37125, + "start": 1732017046556075211 }], [ { @@ -2230,11 +2228,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2245,7 +2243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2256,13 +2254,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2272,9 +2270,9 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2282,10 +2280,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 47750, - "start": 1727952453932917756 + "duration": 37667, + "start": 1732017046556187586 }], [ { @@ -2298,11 +2296,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2313,7 +2311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2324,13 +2322,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2340,9 +2338,9 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2350,10 +2348,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 36333, - "start": 1727952453933030215 + "duration": 34625, + "start": 1732017046556280336 }], [ { @@ -2366,11 +2364,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2381,7 +2379,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2392,13 +2390,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2408,9 +2406,9 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "2756935864759179794", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "14859399231932022925", "type": "test" }, "metrics": { @@ -2418,10 +2416,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 33125, - "start": 1727952453933123215 + "duration": 33917, + "start": 1732017046556369461 }], [ { @@ -2434,11 +2432,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2449,7 +2447,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2460,13 +2458,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2477,9 +2475,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test" }, "metrics": { @@ -2487,12 +2485,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47833, - "start": 1727952453933300840 + "duration": 52667, + "start": 1732017046556557211 }], [ { @@ -2505,11 +2503,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2520,7 +2518,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2531,13 +2529,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.early_flake.abort_reason": "slow", @@ -2549,9 +2547,9 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test" }, "metrics": { @@ -2559,10 +2557,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 51958, - "start": 1727952453933409215 + "duration": 301000040708, + "start": 1732016745556675003 }], [ { @@ -2575,11 +2573,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2590,7 +2588,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2601,13 +2599,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_efd_mix_fail", @@ -2620,9 +2618,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test" }, "metrics": { @@ -2630,12 +2628,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910, + "process_id": 38786, "test.source.end": 12, "test.source.start": 4 }, - "duration": 64917, - "start": 1727952453933520048 + "duration": 55625, + "start": 1732017046556776586 }], [ { @@ -2648,11 +2646,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2663,7 +2661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2674,13 +2672,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2690,9 +2688,9 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test" }, "metrics": { @@ -2700,10 +2698,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 34833, - "start": 1727952453933646590 + "duration": 35625, + "start": 1732017046556891378 }], [ { @@ -2716,11 +2714,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe764500000000", + "_dd.p.tid": "673c7b9600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2731,7 +2729,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-123/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2742,13 +2740,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "11487a99cdf9403fa54218bd33eced63", + "runtime-id": "abe1a8e5930e46b98093e2feda810990", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2758,9 +2756,9 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "14527300999085966890", - "test_session_id": "2843325375140995787", - "test_suite_id": "16967275604786386503", + "test_module_id": "16469156105143312938", + "test_session_id": "8540679216798029288", + "test_suite_id": "12744719041350872463", "type": "test" }, "metrics": { @@ -2768,8 +2766,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 4910 + "process_id": 38786 }, - "duration": 33958, - "start": 1727952453933739048 + "duration": 35917, + "start": 1732017046556981961 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json index 462dbdac481..429b78d489d 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,28 +35,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -64,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 78167, - "start": 1727948741716117885 + "duration": 1234651750, + "start": 1732017027028168925 }], [ { @@ -82,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -97,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -108,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -125,11 +124,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -137,12 +136,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 166083, - "start": 1727948741732948802 + "duration": 96209, + "start": 1732017028281164633 }], [ { @@ -155,11 +154,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -170,7 +169,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -181,13 +180,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -198,11 +197,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -210,12 +209,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 93958, - "start": 1727948741733348677 + "duration": 87959, + "start": 1732017028281384008 }], [ { @@ -228,11 +227,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -243,7 +242,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -254,13 +253,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -271,11 +270,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -283,12 +282,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 57167, - "start": 1727948741733573760 + "duration": 52834, + "start": 1732017028281572008 }], [ { @@ -301,11 +300,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -316,7 +315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -327,13 +326,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -344,11 +343,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -356,12 +355,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 53083, - "start": 1727948741733713177 + "duration": 46916, + "start": 1732017028281702342 }], [ { @@ -374,11 +373,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -389,7 +388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -400,13 +399,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -417,11 +416,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -429,12 +428,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46791, - "start": 1727948741733844344 + "duration": 66458, + "start": 1732017028281827717 }], [ { @@ -447,11 +446,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -462,7 +461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -473,13 +472,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -490,11 +489,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -502,12 +501,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48791, - "start": 1727948741733976219 + "duration": 48750, + "start": 1732017028281971133 }], [ { @@ -520,11 +519,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -535,7 +534,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -546,13 +545,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -563,11 +562,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -575,12 +574,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47584, - "start": 1727948741734130385 + "duration": 45625, + "start": 1732017028282096758 }], [ { @@ -593,11 +592,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -608,7 +607,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -619,13 +618,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -636,11 +635,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -648,12 +647,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 41750, - "start": 1727948741734251427 + "duration": 47792, + "start": 1732017028282217758 }], [ { @@ -666,11 +665,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -681,7 +680,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -692,13 +691,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -709,11 +708,11 @@ "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -721,12 +720,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42208, - "start": 1727948741734369677 + "duration": 45416, + "start": 1732017028282340467 }], [ { @@ -739,11 +738,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -754,7 +753,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -765,27 +764,28 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -793,12 +793,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234560046, - "start": 1727948741716014802 + "duration": 46375, + "start": 1732017028282461050 }], [ { @@ -811,11 +811,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +826,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,27 +837,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", - "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", - "test.status": "fail", + "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -865,10 +864,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 33208, - "start": 1727948741734615969 + "duration": 6543256458, + "start": 1732017021739387925 }], [ { @@ -881,11 +880,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -896,7 +895,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -907,13 +906,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -925,9 +924,9 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -935,10 +934,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 31500, - "start": 1727948741734723469 + "duration": 35584, + "start": 1732017028282723133 }], [ { @@ -951,11 +950,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -966,7 +965,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -977,13 +976,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -993,11 +992,11 @@ "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", - "test.status": "pass", + "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1005,10 +1004,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 32167, - "start": 1727948741734826135 + "duration": 119500, + "start": 1732017028282831967 }], [ { @@ -1021,11 +1020,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1036,7 +1035,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1047,13 +1046,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1065,9 +1064,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1075,10 +1074,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 30958, - "start": 1727948741734933427 + "duration": 36458, + "start": 1732017028283035217 }], [ { @@ -1091,11 +1090,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1106,7 +1105,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1117,13 +1116,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1135,9 +1134,9 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1145,10 +1144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 31250, - "start": 1727948741735034344 + "duration": 34709, + "start": 1732017028283147133 }], [ { @@ -1161,11 +1160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1176,7 +1175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1187,26 +1186,27 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", "test.is_new": "true", + "test.is_retry": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1214,10 +1214,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 6543209975, - "start": 1727948741734567177 + "duration": 34667, + "start": 1732017028283255925 }], [ { @@ -1230,11 +1230,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,13 +1256,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_efd_mix_pass", @@ -1274,9 +1274,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1284,12 +1284,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 12, "test.source.start": 4 }, - "duration": 69666, - "start": 1727948741735699344 + "duration": 76083, + "start": 1732017028283367175 }], [ { @@ -1302,11 +1302,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1317,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,27 +1328,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p1", "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1356,10 +1355,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 38959, - "start": 1727948741735833260 + "duration": 39667, + "start": 1732017028283505883 }], [ { @@ -1372,11 +1371,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1387,7 +1386,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1398,27 +1397,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t4_p2_id", "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test" }, "metrics": { @@ -1426,10 +1424,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 37666, - "start": 1727948741735949594 + "duration": 37125, + "start": 1732017028283606383 }], [ { @@ -1442,11 +1440,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1457,7 +1455,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1468,13 +1466,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_pass", @@ -1483,7 +1481,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "9324705277588330367", + "test_session_id": "13096961797776614253", "type": "test_session_end" }, "metrics": { @@ -1491,10 +1489,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 22319542, - "start": 1727948741715600385 + "duration": 23619459, + "start": 1732017028262062008 }, { "name": "test_visibility.module", @@ -1506,11 +1504,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1521,7 +1519,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1532,12 +1530,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_pass", @@ -1547,8 +1545,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", "type": "test_module_end" }, "metrics": { @@ -1556,8 +1554,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20212167, - "start": 1727948741715958760 + "duration": 21200959, + "start": 1732017028262645133 }, { "name": "test_visibility.suite", @@ -1569,11 +1567,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1584,7 +1582,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1595,12 +1593,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1609,9 +1607,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "3289694753149315000", - "test_session_id": "9324705277588330367", - "test_suite_id": "7093471219527767256", + "test_module_id": "15860744644384930388", + "test_session_id": "13096961797776614253", + "test_suite_id": "7512659030108198446", "type": "test_suite_end" }, "metrics": { @@ -1619,8 +1617,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20087750, - "start": 1727948741715984302 + "duration": 21060625, + "start": 1732017028262685550 }, { "name": "test_visibility.module", @@ -1632,11 +1630,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1647,7 +1645,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1658,12 +1656,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_mix_pass", @@ -1673,8 +1671,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", "type": "test_module_end" }, "metrics": { @@ -1682,8 +1680,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1610750, - "start": 1727948741736223344 + "duration": 1657291, + "start": 1732017028283910592 }, { "name": "test_visibility.suite", @@ -1695,11 +1693,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1710,7 +1708,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1721,12 +1719,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1735,9 +1733,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test_suite_end" }, "metrics": { @@ -1745,8 +1743,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 901916, - "start": 1727948741736245844 + "duration": 902875, + "start": 1732017028283934508 }, { "name": "test_visibility.suite", @@ -1758,11 +1756,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1773,7 +1771,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1784,12 +1782,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1798,9 +1796,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test_suite_end" }, "metrics": { @@ -1808,8 +1806,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 563291, - "start": 1727948741737199094 + "duration": 583333, + "start": 1732017028284896842 }], [ { @@ -1822,11 +1820,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1837,7 +1835,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1848,13 +1846,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1864,9 +1862,9 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -1874,10 +1872,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 38750, - "start": 1727948741736265885 + "duration": 39375, + "start": 1732017028283955592 }], [ { @@ -1890,11 +1888,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1905,7 +1903,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1916,13 +1914,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1932,9 +1930,9 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -1942,10 +1940,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 36375, - "start": 1727948741736367385 + "duration": 36334, + "start": 1732017028284053383 }], [ { @@ -1958,11 +1956,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1973,7 +1971,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1984,13 +1982,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2000,9 +1998,9 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2010,10 +2008,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 34500, - "start": 1727948741736464510 + "duration": 38791, + "start": 1732017028284149717 }], [ { @@ -2026,11 +2024,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2041,7 +2039,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2052,13 +2050,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2068,9 +2066,9 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2078,10 +2076,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 36334, - "start": 1727948741736558010 + "duration": 35166, + "start": 1732017028284245967 }], [ { @@ -2094,11 +2092,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2109,7 +2107,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2120,13 +2118,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2136,9 +2134,9 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2146,10 +2144,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 36250, - "start": 1727948741736657385 + "duration": 40209, + "start": 1732017028284340883 }], [ { @@ -2162,11 +2160,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2177,7 +2175,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2188,13 +2186,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2204,9 +2202,9 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2214,10 +2212,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 35292, - "start": 1727948741736752177 + "duration": 35500, + "start": 1732017028284441008 }], [ { @@ -2230,11 +2228,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2245,7 +2243,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2256,13 +2254,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2272,9 +2270,9 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2282,10 +2280,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 34292, - "start": 1727948741736842760 + "duration": 37792, + "start": 1732017028284536050 }], [ { @@ -2298,11 +2296,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2313,7 +2311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2324,13 +2322,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2340,9 +2338,9 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2350,10 +2348,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 35000, - "start": 1727948741736933177 + "duration": 34750, + "start": 1732017028284629467 }], [ { @@ -2366,11 +2364,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2381,7 +2379,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2392,13 +2390,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2408,9 +2406,9 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "5571252985022706881", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "10499803900482435818", "type": "test" }, "metrics": { @@ -2418,10 +2416,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 34083, - "start": 1727948741737025802 + "duration": 36833, + "start": 1732017028284720675 }], [ { @@ -2434,11 +2432,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2449,7 +2447,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2460,13 +2458,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2477,9 +2475,9 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test" }, "metrics": { @@ -2487,12 +2485,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47208, - "start": 1727948741737218802 + "duration": 52042, + "start": 1732017028284917425 }], [ { @@ -2505,11 +2503,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2520,7 +2518,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2531,13 +2529,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.early_flake.abort_reason": "slow", @@ -2549,9 +2547,9 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test" }, "metrics": { @@ -2559,10 +2557,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 52333, - "start": 1727948741737326719 + "duration": 301000042833, + "start": 1732016727285032342 }], [ { @@ -2575,11 +2573,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2590,7 +2588,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2601,13 +2599,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_efd_mix_pass", @@ -2620,9 +2618,9 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test" }, "metrics": { @@ -2630,12 +2628,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529, + "process_id": 38693, "test.source.end": 12, "test.source.start": 4 }, - "duration": 54208, - "start": 1727948741737437302 + "duration": 60667, + "start": 1732017028285140300 }], [ { @@ -2648,11 +2646,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2663,7 +2661,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2674,13 +2672,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2690,9 +2688,9 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test" }, "metrics": { @@ -2700,10 +2698,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 35833, - "start": 1727948741737553552 + "duration": 39000, + "start": 1732017028285264008 }], [ { @@ -2716,11 +2714,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_efd_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe67c500000000", + "_dd.p.tid": "673c7b8400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2731,7 +2729,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-105/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2742,13 +2740,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0334fe1b2ead46c5a2dea92045f26b6c", + "runtime-id": "feec2a03be4b4409b4d460473f0578c5", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_efd_mix_pass", "test.framework": "dd_manual_test_fw", @@ -2758,9 +2756,9 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16743202330765666736", - "test_session_id": "9324705277588330367", - "test_suite_id": "16222227165286706485", + "test_module_id": "7734405348027363975", + "test_session_id": "13096961797776614253", + "test_suite_id": "116850239999929103", "type": "test" }, "metrics": { @@ -2768,8 +2766,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 97529 + "process_id": 38693 }, - "duration": 34084, - "start": 1727948741737648510 + "duration": 40291, + "start": 1732017028285362717 }]] From 5724fd30074245127c3ce9f47a7a38c0714c5165 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Tue, 19 Nov 2024 09:59:43 -0500 Subject: [PATCH 179/372] fix: [SVLS-5953] pointers for deserialized DynamoDB requests (#11420) Fixes https://github.com/DataDog/dd-trace-py/issues/11320 . The resource-based APIs for DynamoDB in boto3 accept deserialized python values and turn them into proper type-tagged DynamoDB API objects in the background. Unfortunately we don't seem to have access to those lower-level type-tagged objects, so if we see the deserialized forms we need to transform them ourselves in order to correctly extract the key values for hashing into span pointers. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/116ff5f.txt | 59 ---- .riot/requirements/1308524.txt | 59 ---- .riot/requirements/159f4bb.txt | 46 +++ .riot/requirements/15e6ff4.txt | 46 +++ .riot/requirements/15e8745.txt | 46 +++ .riot/requirements/1793deb.txt | 50 +++ .riot/requirements/17a3ee7.txt | 61 ---- .riot/requirements/18d037c.txt | 48 +++ .../requirements/{3fd2b6b.txt => 19065a8.txt} | 15 +- .../requirements/{a3b9edc.txt => 1a4f181.txt} | 13 +- .riot/requirements/1aa24de.txt | 50 +++ .riot/requirements/1b85047.txt | 59 ---- .riot/requirements/469c997.txt | 59 ---- .riot/requirements/58df0ac.txt | 63 ---- .riot/requirements/64d7755.txt | 63 ---- .riot/requirements/921bc6c.txt | 46 +++ ddtrace/_trace/_span_pointer.py | 2 +- .../utils_botocore/span_pointers/__init__.py | 2 +- .../utils_botocore/span_pointers/dynamodb.py | 126 ++++++-- .../_trace/utils_botocore/span_pointers/s3.py | 6 +- ...ed-dynamodb-requests-39b1235a102dab7c.yaml | 4 + riotfile.py | 1 + .../utils_botocore/test_span_pointers.py | 290 +++++++++++++++++- 23 files changed, 737 insertions(+), 477 deletions(-) delete mode 100644 .riot/requirements/116ff5f.txt delete mode 100644 .riot/requirements/1308524.txt create mode 100644 .riot/requirements/159f4bb.txt create mode 100644 .riot/requirements/15e6ff4.txt create mode 100644 .riot/requirements/15e8745.txt create mode 100644 .riot/requirements/1793deb.txt delete mode 100644 .riot/requirements/17a3ee7.txt create mode 100644 .riot/requirements/18d037c.txt rename .riot/requirements/{3fd2b6b.txt => 19065a8.txt} (77%) rename .riot/requirements/{a3b9edc.txt => 1a4f181.txt} (79%) create mode 100644 .riot/requirements/1aa24de.txt delete mode 100644 .riot/requirements/1b85047.txt delete mode 100644 .riot/requirements/469c997.txt delete mode 100644 .riot/requirements/58df0ac.txt delete mode 100644 .riot/requirements/64d7755.txt create mode 100644 .riot/requirements/921bc6c.txt create mode 100644 releasenotes/notes/fix-span-pointer-deserialized-dynamodb-requests-39b1235a102dab7c.yaml diff --git a/.riot/requirements/116ff5f.txt b/.riot/requirements/116ff5f.txt deleted file mode 100644 index 6d413b5ec45..00000000000 --- a/.riot/requirements/116ff5f.txt +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/116ff5f.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/1308524.txt b/.riot/requirements/1308524.txt deleted file mode 100644 index 0561b96fb8f..00000000000 --- a/.riot/requirements/1308524.txt +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1308524.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/159f4bb.txt b/.riot/requirements/159f4bb.txt new file mode 100644 index 00000000000..41cf05bdf26 --- /dev/null +++ b/.riot/requirements/159f4bb.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/159f4bb.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/.riot/requirements/15e6ff4.txt b/.riot/requirements/15e6ff4.txt new file mode 100644 index 00000000000..205310cd885 --- /dev/null +++ b/.riot/requirements/15e6ff4.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/15e6ff4.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/.riot/requirements/15e8745.txt b/.riot/requirements/15e8745.txt new file mode 100644 index 00000000000..08c33f59162 --- /dev/null +++ b/.riot/requirements/15e8745.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/15e8745.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/.riot/requirements/1793deb.txt b/.riot/requirements/1793deb.txt new file mode 100644 index 00000000000..9002bd898b2 --- /dev/null +++ b/.riot/requirements/1793deb.txt @@ -0,0 +1,50 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1793deb.in +# +annotated-types==0.7.0 +anyio==4.5.2 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +tomli==2.1.0 +typing-extensions==4.12.2 +urllib3==1.26.20 +wheel==0.45.0 +zipp==3.20.2 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.3.0 diff --git a/.riot/requirements/17a3ee7.txt b/.riot/requirements/17a3ee7.txt deleted file mode 100644 index 14803271515..00000000000 --- a/.riot/requirements/17a3ee7.txt +++ /dev/null @@ -1,61 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/17a3ee7.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -exceptiongroup==1.2.2 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -tomli==2.0.1 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/18d037c.txt b/.riot/requirements/18d037c.txt new file mode 100644 index 00000000000..5e22bd1f37d --- /dev/null +++ b/.riot/requirements/18d037c.txt @@ -0,0 +1,48 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18d037c.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +exceptiongroup==1.2.2 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +tomli==2.1.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/.riot/requirements/3fd2b6b.txt b/.riot/requirements/19065a8.txt similarity index 77% rename from .riot/requirements/3fd2b6b.txt rename to .riot/requirements/19065a8.txt index e1f8ac86bc1..c029f9be123 100644 --- a/.riot/requirements/3fd2b6b.txt +++ b/.riot/requirements/19065a8.txt @@ -2,12 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/3fd2b6b.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/19065a8.in # annotated-types==0.5.0 anyio==3.7.1 -attrs==23.2.0 -certifi==2024.7.4 +attrs==24.2.0 +boto3==1.33.13 +botocore==1.33.13 +certifi==2024.8.30 coverage[toml]==7.2.7 exceptiongroup==1.2.2 fastapi==0.103.2 @@ -16,9 +18,10 @@ httpcore==0.17.3 httpretty==1.1.4 httpx==0.24.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 importlib-metadata==6.7.0 iniconfig==2.0.0 +jmespath==1.0.1 mock==5.1.0 msgpack==1.0.5 opentracing==2.4.0 @@ -30,12 +33,16 @@ pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 +python-dateutil==2.9.0.post0 +s3transfer==0.8.2 +six==1.16.0 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.27.0 structlog==23.1.0 tomli==2.0.1 typing-extensions==4.7.1 +urllib3==1.26.20 wheel==0.42.0 zipp==3.15.0 diff --git a/.riot/requirements/a3b9edc.txt b/.riot/requirements/1a4f181.txt similarity index 79% rename from .riot/requirements/a3b9edc.txt rename to .riot/requirements/1a4f181.txt index 9321cf36442..f01b5c2b169 100644 --- a/.riot/requirements/a3b9edc.txt +++ b/.riot/requirements/1a4f181.txt @@ -2,13 +2,15 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/a3b9edc.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1a4f181.in # annotated-types==0.5.0 anyio==3.7.1 attrs==22.1.0 +boto3==1.33.13 +botocore==1.33.13 cattrs==23.1.2 -certifi==2024.7.4 +certifi==2024.8.30 coverage[toml]==7.2.7 exceptiongroup==1.2.2 fastapi==0.103.2 @@ -17,9 +19,10 @@ httpcore==0.17.3 httpretty==1.1.4 httpx==0.24.1 hypothesis==6.45.0 -idna==3.7 +idna==3.10 importlib-metadata==6.7.0 iniconfig==2.0.0 +jmespath==1.0.1 mock==5.1.0 msgpack==1.0.5 opentracing==2.4.0 @@ -31,12 +34,16 @@ pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 +python-dateutil==2.9.0.post0 +s3transfer==0.8.2 +six==1.16.0 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.27.0 structlog==23.1.0 tomli==2.0.1 typing-extensions==4.7.1 +urllib3==1.26.20 wheel==0.42.0 zipp==3.15.0 diff --git a/.riot/requirements/1aa24de.txt b/.riot/requirements/1aa24de.txt new file mode 100644 index 00000000000..d61dfa8fa6b --- /dev/null +++ b/.riot/requirements/1aa24de.txt @@ -0,0 +1,50 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1aa24de.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +exceptiongroup==1.2.2 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +tomli==2.1.0 +typing-extensions==4.12.2 +urllib3==1.26.20 +wheel==0.45.0 +zipp==3.21.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/.riot/requirements/1b85047.txt b/.riot/requirements/1b85047.txt deleted file mode 100644 index c0e05fb634e..00000000000 --- a/.riot/requirements/1b85047.txt +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b85047.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/469c997.txt b/.riot/requirements/469c997.txt deleted file mode 100644 index ccaab5d112c..00000000000 --- a/.riot/requirements/469c997.txt +++ /dev/null @@ -1,59 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/469c997.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/58df0ac.txt b/.riot/requirements/58df0ac.txt deleted file mode 100644 index dc7414c816f..00000000000 --- a/.riot/requirements/58df0ac.txt +++ /dev/null @@ -1,63 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/58df0ac.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -exceptiongroup==1.2.2 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -tomli==2.0.1 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/64d7755.txt b/.riot/requirements/64d7755.txt deleted file mode 100644 index 1d4859c8405..00000000000 --- a/.riot/requirements/64d7755.txt +++ /dev/null @@ -1,63 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/64d7755.in -# -annotated-types==0.7.0 -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.7.4 -click==8.1.7 -coverage[toml]==7.6.0 -dnspython==2.6.1 -email-validator==2.2.0 -exceptiongroup==1.2.2 -fastapi==0.111.1 -fastapi-cli==0.0.4 -h11==0.14.0 -httpcore==1.0.5 -httpretty==1.1.4 -httptools==0.6.1 -httpx==0.27.0 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markdown-it-py==3.0.0 -markupsafe==2.1.5 -mdurl==0.1.2 -mock==5.1.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pydantic==2.8.2 -pydantic-core==2.20.1 -pygments==2.18.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dotenv==1.0.1 -python-multipart==0.0.9 -pyyaml==6.0.1 -rich==13.7.1 -shellingham==1.5.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.37.2 -structlog==24.4.0 -tomli==2.0.1 -typer==0.12.3 -typing-extensions==4.12.2 -uvicorn[standard]==0.30.3 -uvloop==0.19.0 -watchfiles==0.22.0 -websockets==12.0 -wheel==0.43.0 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/921bc6c.txt b/.riot/requirements/921bc6c.txt new file mode 100644 index 00000000000..fd44244070f --- /dev/null +++ b/.riot/requirements/921bc6c.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/921bc6c.in +# +annotated-types==0.7.0 +anyio==4.6.2.post1 +attrs==24.2.0 +boto3==1.35.62 +botocore==1.35.62 +certifi==2024.8.30 +coverage[toml]==7.6.7 +fastapi==0.115.5 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.3 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.2 +structlog==24.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.5.0 diff --git a/ddtrace/_trace/_span_pointer.py b/ddtrace/_trace/_span_pointer.py index 015aac7fc12..3b93980335f 100644 --- a/ddtrace/_trace/_span_pointer.py +++ b/ddtrace/_trace/_span_pointer.py @@ -79,7 +79,7 @@ def _standard_hashing_function(*elements: bytes) -> str: def _standard_hashing_function_failure(reason: str) -> str: log.debug( - "failed to generate standard hash for span pointer: %s", + "span pointers: failed to generate standard hash for span pointer: %s", reason, ) record_span_pointer_calculation_issue( diff --git a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py index d4d724c38cb..c9dfe7327d2 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/__init__.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/__init__.py @@ -42,7 +42,7 @@ def extract_span_pointers_from_successful_botocore_response( except Exception as e: # Catch-all in case we miss something in the helpers - log.debug("Error extracting span pointers from botocore response: %s", e) + log.debug("span pointers: Error extracting span pointers from botocore response: %s", e) record_span_pointer_calculation_issue("extractor_root", "unexpected_error") record_span_pointer_calculation(span_pointer_count=len(result)) diff --git a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py index 89cc8c6f6e8..2aa140a0556 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py @@ -25,6 +25,16 @@ log = get_logger(__name__) +def _boto3_dynamodb_types_TypeSerializer_serialize(value): + # We need this serializer for some of the code below, but we don't want to + # import boto3 things at the top level of this module since not everyone + # who is using ddtrace also needs boto3. Any code that actually does reach + # the serialization functionality below *will* have boto3 available. + from boto3.dynamodb.types import TypeSerializer + + return TypeSerializer().serialize(value) + + class _TelemetryIssueTags(Enum): REQUEST_PARAMETERS = "request_parameters" HASHING_FAILURE = "hashing_failure" @@ -37,7 +47,15 @@ class _TelemetryIssueTags(Enum): _DynamoDBItemFieldName = str _DynamoDBItemTypeTag = str -_DynamoDBItemValue = Dict[_DynamoDBItemTypeTag, Any] +# _DynamoDBItemValueObject is shaped like {"S": "something"}, the form that the +# lower level DynamoDB API expects. +_DynamoDBItemValueObject = Dict[_DynamoDBItemTypeTag, Any] +# _DynamoDBItemValueDeserialized is the python-native form of the value. The +# resource-based boto3 APIs for DynamoDB accept this form and handle the +# serialization into something like the _DyanmoDBItemValueObject using the +# TypeSerializer. +_DynamoDBItemValueDeserialized = Any +_DynamoDBItemValue = Union[_DynamoDBItemValueObject, _DynamoDBItemValueDeserialized] _DynamoDBItem = Dict[_DynamoDBItemFieldName, _DynamoDBItemValue] _DynamoDBItemPrimaryKeyValue = Dict[_DynamoDBItemTypeTag, str] # must be length 1 @@ -160,7 +178,7 @@ def _extract_span_pointers_for_dynamodb_putitem_response( item = request_parameters["Item"] except KeyError as e: log.debug( - "failed to extract %s span pointer: missing key %s", + "span pointers: failed to extract %s span pointer: missing key %s", operation, e, ) @@ -199,7 +217,7 @@ def _extract_span_pointers_for_dynamodb_putitem_response( except Exception as e: log.debug( - "failed to generate %s span pointer: %s", + "span pointers: failed to generate %s span pointer: %s", operation, e, ) @@ -216,7 +234,7 @@ def _extract_primary_key_names_from_configuration( return dynamodb_primary_key_names_for_tables[table_name] except KeyError as e: log.warning( - "failed to extract %s span pointer: table %s not found in primary key names", + "span pointers: failed to extract %s span pointer: table %s not found in primary key names", operation, e, ) @@ -237,7 +255,7 @@ def _extract_span_pointers_for_dynamodb_keyed_operation_response( key = request_parmeters["Key"] except KeyError as e: log.debug( - "failed to extract %s span pointer: missing key %s", + "span pointers: failed to extract %s span pointer: missing key %s", operation, e, ) @@ -260,7 +278,7 @@ def _extract_span_pointers_for_dynamodb_keyed_operation_response( except Exception as e: log.debug( - "failed to generate %s span pointer: %s", + "span pointers: failed to generate %s span pointer: %s", operation, e, ) @@ -285,7 +303,7 @@ def _extract_span_pointers_for_dynamodb_batchwriteitem_response( except Exception as e: log.debug( - "failed to extract %s span pointers: %s", + "span pointers: failed to extract %s span pointers: %s", operation, e, ) @@ -321,7 +339,7 @@ def _extract_span_pointers_for_dynamodb_batchwriteitem_response( except Exception as e: log.debug( - "failed to generate %s span pointer: %s", + "span pointers: failed to generate %s span pointer: %s", operation, e, ) @@ -347,7 +365,7 @@ def _extract_span_pointers_for_dynamodb_transactwriteitems_response( except Exception as e: log.debug( - "failed to generate %s span pointer: %s", + "span pointers: failed to generate %s span pointer: %s", operation, e, ) @@ -364,7 +382,7 @@ def _identify_dynamodb_batch_write_item_processed_items( processed_items = {} if not all(table_name in requested_items for table_name in unprocessed_items): - log.debug("%s unprocessed items include tables not in the requested items", operation) + log.debug("span pointers: %s unprocessed items include tables not in the requested items", operation) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PROCESSED_ITEMS_CALCULATION.value ) @@ -380,7 +398,7 @@ def _identify_dynamodb_batch_write_item_processed_items( for unprocessed_write_request in unprocessed_items[table_name] ): log.debug( - "%s unprocessed write requests include items not in the requested write requests", + "span pointers: %s unprocessed write requests include items not in the requested write requests", operation, ) record_span_pointer_calculation_issue( @@ -406,7 +424,7 @@ def _aws_dynamodb_item_primary_key_from_item( item: _DynamoDBItem, ) -> Optional[_DynamoDBItemPrimaryKey]: if len(primary_key_field_names) not in (1, 2): - log.debug("unexpected number of primary key fields: %d", len(primary_key_field_names)) + log.debug("span pointers: unexpected number of primary key fields: %d", len(primary_key_field_names)) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) @@ -435,7 +453,7 @@ def _aws_dynamodb_item_primary_key_from_write_request( operation = _OPERATION_BASE + "BatchWriteItem" if len(write_request) != 1: - log.debug("unexpected number of write request fields: %d", len(write_request)) + log.debug("span pointers: unexpected number of write request fields: %d", len(write_request)) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) @@ -470,7 +488,7 @@ def _aws_dynamodb_item_primary_key_from_write_request( return write_request["DeleteRequest"]["Key"] else: - log.debug("unexpected write request structure: %s", "".join(sorted(write_request.keys()))) + log.debug("span pointers: unexpected write request structure: %s", "".join(sorted(write_request.keys()))) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) @@ -484,7 +502,7 @@ def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( operation = _OPERATION_BASE + "TransactWriteItems" if len(transact_write_request) != 1: - log.debug("unexpected number of transact write request fields: %d", len(transact_write_request)) + log.debug("span pointers: unexpected number of transact write request fields: %d", len(transact_write_request)) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) @@ -542,7 +560,10 @@ def _aws_dynamodb_item_span_pointer_description_for_transactwrite_request( key = transact_write_request["Update"]["Key"] else: - log.debug("unexpected transact write request structure: %s", "".join(sorted(transact_write_request.keys()))) + log.debug( + "span pointers: unexpected transact write request structure: %s", + "".join(sorted(transact_write_request.keys())), + ) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.REQUEST_PARAMETERS.value ) @@ -584,16 +605,25 @@ def _aws_dynamodb_extract_and_verify_primary_key_field_value_item( primary_key_field_name: _DynamoDBItemFieldName, ) -> Optional[_DynamoDBItemPrimaryKeyValue]: if primary_key_field_name not in item: - log.debug("missing primary key field: %s", primary_key_field_name) + log.debug("span pointers: missing primary key field: %s", primary_key_field_name) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) return None - value_object = item[primary_key_field_name] + value_object = _aws_dynamodb_item_value_to_probably_primary_key_value( + operation=operation, + item_value=item[primary_key_field_name], + ) + if value_object is None: + return None if len(value_object) != 1: - log.debug("primary key field %s must have exactly one value: %d", primary_key_field_name, len(value_object)) + log.debug( + "span pointers: primary key field %s must have exactly one value: %d", + primary_key_field_name, + len(value_object), + ) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) @@ -601,14 +631,18 @@ def _aws_dynamodb_extract_and_verify_primary_key_field_value_item( value_type, value_data = next(iter(value_object.items())) if value_type not in ("S", "N", "B"): - log.debug("unexpected primary key field %s value type: %s", primary_key_field_name, value_type) + log.debug("span pointers: unexpected primary key field %s value type: %s", primary_key_field_name, value_type) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) return None if not isinstance(value_data, str): - log.debug("unexpected primary key field %s value data type: %s", primary_key_field_name, type(value_data)) + log.debug( + "span pointers: unexpected primary key field %s value data type: %s", + primary_key_field_name, + type(value_data), + ) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) @@ -617,6 +651,31 @@ def _aws_dynamodb_extract_and_verify_primary_key_field_value_item( return {value_type: value_data} +def _aws_dynamodb_item_value_to_probably_primary_key_value( + operation: str, item_value: _DynamoDBItemValue +) -> Optional[_DynamoDBItemPrimaryKeyValue]: + # If the item_value looks more or less like a primary key, we return it and + # let the caller decide what to do with it. Otherwise we use the type + # serializer and hope that does the right thing. + + if ( + isinstance(item_value, dict) + and len(item_value) == 1 + and all(isinstance(part, str) for part in itertools.chain.from_iterable(item_value.items())) + ): + return item_value + + try: + return cast(_DynamoDBItemPrimaryKeyValue, _boto3_dynamodb_types_TypeSerializer_serialize(item_value)) + + except Exception as e: + log.debug("span pointers: failed to serialize item value to botocore value: %s", e) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None + + def _aws_dynamodb_item_span_pointer_hash( operation: str, table_name: _DynamoDBTableName, primary_key: _DynamoDBItemPrimaryKey ) -> Optional[str]: @@ -650,7 +709,7 @@ def _aws_dynamodb_item_span_pointer_hash( encoded_value_2 = maybe_encoded_value_2 else: - log.debug("unexpected number of primary key fields: %d", len(primary_key)) + log.debug("span pointers: unexpected number of primary key fields: %d", len(primary_key)) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) @@ -665,7 +724,7 @@ def _aws_dynamodb_item_span_pointer_hash( ) except Exception as e: - log.debug("failed to generate %s span pointer hash: %s", operation, e) + log.debug("span pointers: failed to generate %s span pointer hash: %s", operation, e) record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) return None @@ -674,8 +733,18 @@ def _aws_dynamodb_item_encode_primary_key_value( operation: str, value_object: _DynamoDBItemPrimaryKeyValue ) -> Optional[bytes]: try: + if not isinstance(value_object, dict): + try: + value_object = _boto3_dynamodb_types_TypeSerializer_serialize(value_object) + except Exception as e: + log.debug("span pointers: failed to serialize primary key value to botocore value: %s", e) + record_span_pointer_calculation_issue( + operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value + ) + return None + if len(value_object) != 1: - log.debug("primary key value object must have exactly one field: %d", len(value_object)) + log.debug("span pointers: primary key value object must have exactly one field: %d", len(value_object)) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) @@ -687,17 +756,20 @@ def _aws_dynamodb_item_encode_primary_key_value( return value.encode("utf-8") if value_type in ("N", "B"): - # these should already be here as ASCII strings + # these should already be here as ASCII strings, though B is + # sometimes already bytes. + if isinstance(value, bytes): + return value return value.encode("ascii") - log.debug("unexpected primary key value type: %s", value_type) + log.debug("span pointers: unexpected primary key value type: %s", value_type) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) return None except Exception as e: - log.debug("failed to encode primary key value for %s: %s", operation, e) + log.debug("span pointers: failed to encode primary key value for %s: %s", operation, e) record_span_pointer_calculation_issue( operation=operation, issue_tag=_TelemetryIssueTags.PRIMARY_KEY_ISSUE.value ) diff --git a/ddtrace/_trace/utils_botocore/span_pointers/s3.py b/ddtrace/_trace/utils_botocore/span_pointers/s3.py index 46625292da1..f841cc1de36 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/s3.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/s3.py @@ -97,7 +97,7 @@ def _extract_span_pointers_for_s3_response_with_helper( except Exception as e: log.debug( - "problem with parameters for %s span pointer: %s", + "span pointers: problem with parameters for %s span pointer: %s", operation, e, ) @@ -143,7 +143,7 @@ def _aws_s3_object_span_pointer_hash(operation: str, bucket: str, key: str, etag # Some AWS API endpoints put the ETag in double quotes. We expect the # calling code to have correctly fixed this already. log.debug( - "ETag should not have double quotes: %s", + "span pointers: ETag should not have double quotes: %s", etag, ) record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.ETAG_QUOTES.value) @@ -158,7 +158,7 @@ def _aws_s3_object_span_pointer_hash(operation: str, bucket: str, key: str, etag except Exception as e: log.debug( - "failed to hash S3 object span pointer: %s", + "span pointers: failed to hash S3 object span pointer: %s", e, ) record_span_pointer_calculation_issue(operation=operation, issue_tag=_TelemetryIssueTags.HASHING_FAILURE.value) diff --git a/releasenotes/notes/fix-span-pointer-deserialized-dynamodb-requests-39b1235a102dab7c.yaml b/releasenotes/notes/fix-span-pointer-deserialized-dynamodb-requests-39b1235a102dab7c.yaml new file mode 100644 index 00000000000..5ddf68ddbf9 --- /dev/null +++ b/releasenotes/notes/fix-span-pointer-deserialized-dynamodb-requests-39b1235a102dab7c.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + botocore: This fix resolves the issue where the span pointer for deserialized DynamoDB requests (through the resource-based API) were not being generated. diff --git a/riotfile.py b/riotfile.py index 6c9f13fc62f..64829e61b96 100644 --- a/riotfile.py +++ b/riotfile.py @@ -296,6 +296,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "httpx": latest, "pytest-randomly": latest, "setuptools": latest, + "boto3": latest, }, env={ "DD_CIVISIBILITY_LOG_LEVEL": "none", diff --git a/tests/tracer/utils_botocore/test_span_pointers.py b/tests/tracer/utils_botocore/test_span_pointers.py index 775ad23d6bd..457f92712ea 100644 --- a/tests/tracer/utils_botocore/test_span_pointers.py +++ b/tests/tracer/utils_botocore/test_span_pointers.py @@ -1,3 +1,4 @@ +from decimal import Decimal import logging import re from typing import Dict @@ -5,6 +6,7 @@ from typing import NamedTuple from typing import Optional from typing import Set +from typing import Union import mock import pytest @@ -71,7 +73,7 @@ class TestDynamodbItemPointer: class HashingCase(NamedTuple): name: str table_name: str - primary_key: Dict[str, Dict[str, str]] + primary_key: Dict[str, Union[Dict[str, str], str, Decimal, bytes]] pointer_hash: str @pytest.mark.parametrize( @@ -83,18 +85,36 @@ class HashingCase(NamedTuple): primary_key={"some-key": {"S": "some-value"}}, pointer_hash="7f1aee721472bcb48701d45c7c7f7821", ), + HashingCase( + name="one string primary key deserializd", + table_name="some-table", + primary_key={"some-key": "some-value"}, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + ), HashingCase( name="one binary primary key", table_name="some-table", primary_key={"some-key": {"B": "c29tZS12YWx1ZQo="}}, pointer_hash="cc789e5ea89c317ac58af92d7a1ba2c2", ), + HashingCase( + name="one binary primary key deserialized", + table_name="some-table", + primary_key={"some-key": b"c29tZS12YWx1ZQo="}, + pointer_hash="cc789e5ea89c317ac58af92d7a1ba2c2", + ), HashingCase( name="one number primary key", table_name="some-table", primary_key={"some-key": {"N": "123.456"}}, pointer_hash="434a6dba3997ce4dbbadc98d87a0cc24", ), + HashingCase( + name="one number primary key deserialized", + table_name="some-table", + primary_key={"some-key": Decimal("123.456")}, + pointer_hash="434a6dba3997ce4dbbadc98d87a0cc24", + ), HashingCase( name="string and number primary key", table_name="some-table", @@ -104,6 +124,15 @@ class HashingCase(NamedTuple): }, pointer_hash="7aa1b80b0e49bd2078a5453399f4dd67", ), + HashingCase( + name="string and number primary key deserialized", + table_name="some-table", + primary_key={ + "some-key": "some-value", + "other-key": Decimal("123"), + }, + pointer_hash="7aa1b80b0e49bd2078a5453399f4dd67", + ), HashingCase( name="string and number primary key reversed", table_name="some-table", @@ -172,7 +201,7 @@ class PointersCase(NamedTuple): "ETag": "ab12ef34", }, expected_pointers=[], - expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'Bucket'", + expected_logger_regex=r"span pointers: problem with parameters for S3.PutObject .*: 'Bucket'", logger_level="debug", ), PointersCase( @@ -186,7 +215,7 @@ class PointersCase(NamedTuple): "ETag": "ab12ef34", }, expected_pointers=[], - expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'Key'", + expected_logger_regex=r"span pointers: problem with parameters for S3.PutObject .*: 'Key'", logger_level="debug", ), PointersCase( @@ -199,7 +228,7 @@ class PointersCase(NamedTuple): }, response={}, expected_pointers=[], - expected_logger_regex=r"problem with parameters for S3.PutObject .*: 'ETag'", + expected_logger_regex=r"span pointers: problem with parameters for S3.PutObject .*: 'ETag'", logger_level="debug", ), PointersCase( @@ -379,6 +408,30 @@ class PointersCase(NamedTuple): expected_logger_regex=None, logger_level="debug", ), + PointersCase( + name="dynamodb.PutItem deserialized", + endpoint_name="dynamodb", + operation_name="PutItem", + request_parameters={ + "TableName": "some-table", + "Item": { + "some-key": "some-value", + }, + }, + response={ + # things we do not care about + }, + expected_pointers=[ + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + ], + expected_logger_regex=None, + logger_level="debug", + ), PointersCase( name="dynamodb.PutItem with extra data", endpoint_name="dynamodb", @@ -462,6 +515,30 @@ class PointersCase(NamedTuple): expected_logger_regex=None, logger_level="debug", ), + PointersCase( + name="dynamodb.UpdateItem deserialized", + endpoint_name="dynamodb", + operation_name="UpdateItem", + request_parameters={ + "TableName": "some-table", + "Key": { + "some-key": "some-value", + }, + }, + response={ + # things we do not care about + }, + expected_pointers=[ + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + ], + expected_logger_regex=None, + logger_level="debug", + ), PointersCase( name="dynamodb.UpdateItem table does not need to be known", endpoint_name="dynamodb", @@ -568,6 +645,30 @@ class PointersCase(NamedTuple): expected_logger_regex=None, logger_level="debug", ), + PointersCase( + name="dynamodb.DeleteItem deserialized", + endpoint_name="dynamodb", + operation_name="DeleteItem", + request_parameters={ + "TableName": "some-table", + "Key": { + "some-key": "some-value", + }, + }, + response={ + # things we do not care about + }, + expected_pointers=[ + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + ], + expected_logger_regex=None, + logger_level="debug", + ), PointersCase( name="dynamodb.DeleteItem table does not need to be known", endpoint_name="dynamodb", @@ -729,6 +830,85 @@ class PointersCase(NamedTuple): expected_logger_regex=None, logger_level="debug", ), + PointersCase( + name="dynamodb.BatchWriteItem works with multiple items and tables serialized", + endpoint_name="dynamodb", + operation_name="BatchWriteItem", + request_parameters={ + "RequestItems": { + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": "some-value", + }, + }, + }, + { + "PutRequest": { + "Item": { + "some-key": "will-not-complete", + }, + }, + }, + ], + "unknown-table": [ + { + "DeleteRequest": { + "Key": { + "some-key": "some-value", + }, + }, + }, + { + "PutRequest": { + "Item": { + "some-key": "will-also-not-complete", + }, + }, + }, + ], + }, + }, + response={ + "UnprocessedItems": { + "some-table": [ + { + "PutRequest": { + "Item": { + "some-key": "will-not-complete", + }, + }, + }, + ], + "unknown-table": [ + { + "PutRequest": { + "Item": { + "some-key": "will-also-not-complete", + }, + }, + }, + ], + }, + }, + expected_pointers=[ + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + _SpanPointerDescription( + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="d8840182e4052ee105348b033e0a6810", + extra_attributes={}, + ), + ], + expected_logger_regex=None, + logger_level="debug", + ), PointersCase( name="dynamodb.BatchWriteItem still needs the mapping sometimes", endpoint_name="dynamodb", @@ -821,6 +1001,76 @@ class PointersCase(NamedTuple): expected_logger_regex=None, logger_level="debug", ), + PointersCase( + name="dynamodb.TransactWriteItems basic case deserialized", + endpoint_name="dynamodb", + operation_name="TransactWriteItems", + request_parameters={ + "TransactItems": [ + { + "Put": { + "TableName": "some-table", + "Item": { + "some-key": "some-value", + }, + }, + }, + { + "Delete": { + "TableName": "unknown-table", + "Key": { + "some-key": "some-value", + }, + }, + }, + { + "Update": { + "TableName": "some-table", + "Key": { + "some-key": "some-value", + "other-key": 123, + }, + }, + }, + { + "ConditionCheck": { + "TableName": "do-not-care-table", + "Key": { + "do-not-care-key": "meh", + }, + }, + }, + ], + }, + response={ + # things we do not care about + }, + expected_pointers=[ + _SpanPointerDescription( + # Update + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7aa1b80b0e49bd2078a5453399f4dd67", + extra_attributes={}, + ), + _SpanPointerDescription( + # Put + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="7f1aee721472bcb48701d45c7c7f7821", + extra_attributes={}, + ), + _SpanPointerDescription( + # Delete + pointer_kind="aws.dynamodb.item", + pointer_direction=_SpanPointerDirection.DOWNSTREAM, + pointer_hash="d8840182e4052ee105348b033e0a6810", + extra_attributes={}, + ), + ], + expected_logger_regex=None, + logger_level="debug", + ), PointersCase( name="dynamodb.TransactWriteItems still needs the mapping sometimes", endpoint_name="dynamodb", @@ -863,13 +1113,16 @@ def test_pointers(self, pointers_case: PointersCase) -> None: key=lambda pointer: pointer.pointer_hash, ) == sorted(pointers_case.expected_pointers, key=lambda pointer: pointer.pointer_hash) + span_pointer_log_args = [ + call_args for call_args in mock_logger.call_args_list if call_args[0][0].startswith("span pointers: ") + ] if pointers_case.expected_logger_regex is None: - mock_logger.assert_not_called() + assert not span_pointer_log_args else: - mock_logger.assert_called_once() + assert len(span_pointer_log_args) == 1 - (args, kwargs) = mock_logger.call_args + (args, kwargs) = span_pointer_log_args.pop() assert not kwargs fmt, *other_args = args assert re.match( @@ -933,7 +1186,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key=None, - expected_logger_regex="unexpected number of write request fields", + expected_logger_regex="span pointers: unexpected number of write request fields", ), WriteRequestPrimaryKeyCase( name="unknown request kind", @@ -947,7 +1200,7 @@ class WriteRequestPrimaryKeyCase(NamedTuple): }, }, primary_key=None, - expected_logger_regex="unexpected write request structure: SomeRequest", + expected_logger_regex="span pointers: unexpected write request structure: SomeRequest", ), ], ids=lambda test_case: test_case.name, @@ -965,13 +1218,16 @@ def test_aws_dynamodb_item_primary_key_from_write_request(self, test_case: Write == test_case.primary_key ) + span_pointer_log_args = [ + call_args for call_args in mock_logger.call_args_list if call_args[0][0].startswith("span pointers: ") + ] if test_case.expected_logger_regex is None: - mock_logger.assert_not_called() + assert not span_pointer_log_args else: - mock_logger.assert_called_once() + assert len(span_pointer_log_args) == 1 - (args, kwargs) = mock_logger.call_args + (args, kwargs) = span_pointer_log_args.pop() assert not kwargs fmt, *other_args = args assert re.match( @@ -1178,13 +1434,17 @@ def test_identify_dynamodb_batch_write_item_processed_items(self, test_case: Pro ) assert processed_items == test_case.expected_processed_items + span_pointer_log_args = [ + call_args for call_args in mock_logger.call_args_list if call_args[0][0].startswith("span pointers: ") + ] + if test_case.expected_logger_regex is None: - mock_logger.assert_not_called() + assert not span_pointer_log_args else: - mock_logger.assert_called_once() + assert len(span_pointer_log_args) == 1 - (args, kwargs) = mock_logger.call_args + (args, kwargs) = span_pointer_log_args.pop() assert not kwargs fmt, *other_args = args assert re.match( From 31bfea7fee2eef93642bf51136e084795271d3f1 Mon Sep 17 00:00:00 2001 From: wantsui Date: Tue, 19 Nov 2024 13:23:39 -0500 Subject: [PATCH 180/372] chore: remove duplicate (and expired) flaky marker from the aws lambda test (#11428) There's currently two flaky decorators on the test aws lambda file. `@flaky(1735812000)` -> Jan 2, 2025 `@flaky(1709306303)` -> March 1, 2024 The purpose of this is to remove the expired one. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/aws_lambda/test_aws_lambda.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/contrib/aws_lambda/test_aws_lambda.py b/tests/contrib/aws_lambda/test_aws_lambda.py index f9ee510573f..2de2b286e03 100644 --- a/tests/contrib/aws_lambda/test_aws_lambda.py +++ b/tests/contrib/aws_lambda/test_aws_lambda.py @@ -55,7 +55,6 @@ def setup(): @flaky(1735812000) @pytest.mark.parametrize("customApmFlushDeadline", [("-100"), ("10"), ("100"), ("200")]) @pytest.mark.snapshot -@flaky(1709306303) def test_timeout_traces(context, customApmFlushDeadline): env = get_env( { From cd671e2ec0cb02341b59742d00b00131c6a18a6d Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:19:23 -0500 Subject: [PATCH 181/372] chore(ci): order test suite names in jobspec (#11340) Re-ordered the GitLab test suites to maintain alphabetization. No content change. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/jobspec.yml | 198 +++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/tests/contrib/jobspec.yml b/tests/contrib/jobspec.yml index 32f05864383..a4695b39f77 100644 --- a/tests/contrib/jobspec.yml +++ b/tests/contrib/jobspec.yml @@ -6,16 +6,34 @@ aiohttp: env: SUITE_NAME: aiohttp parallelism: 3 +algoliasearch: + runner: riot + is_snapshot: true + env: + SUITE_NAME: algoliasearch +aredis: + runner: riot + is_snapshot: true + services: + - redis + env: + SUITE_NAME: "aredis$" + parallelism: 3 asgi: runner: riot is_snapshot: true env: SUITE_NAME: "asgi$" -tornado: +asynctest: + runner: riot + env: + SUITE_NAME: "asynctest$" + parallelism: 3 +avro: runner: riot is_snapshot: true env: - SUITE_NAME: tornado + SUITE_NAME: avro bottle: runner: riot is_snapshot: true @@ -157,94 +175,93 @@ httpx: env: SUITE_NAME: httpx parallelism: 2 -molten: +jinja2: runner: riot is_snapshot: true env: - SUITE_NAME: molten -pylibmc: + SUITE_NAME: jinja2 +kafka: runner: riot is_snapshot: true services: - - memcached - env: - SUITE_NAME: pylibmc -asynctest: - runner: riot + - kafka env: - SUITE_NAME: "asynctest$" - parallelism: 3 -pymemcache: + SUITE_NAME: kafka + TEST_KAFKA_HOST: "kafka" + TEST_KAFKA_PORT: "29092" + parallelism: 4 +kombu: runner: riot is_snapshot: true services: - - memcached + - rabbitmq env: - SUITE_NAME: pymemcache -pymongo: + SUITE_NAME: kombu +logbook: runner: riot is_snapshot: true - services: - - mongo env: - SUITE_NAME: pymongo -pynamodb: + SUITE_NAME: logbook +loguru: runner: riot is_snapshot: true env: - SUITE_NAME: pynamodb -pyodbc: + SUITE_NAME: loguru +mako: runner: riot is_snapshot: true env: - SUITE_NAME: pyodbc -pyramid: + SUITE_NAME: mako +molten: runner: riot is_snapshot: true env: - SUITE_NAME: pyramid -requests: + SUITE_NAME: molten +opentracer: + runner: riot + env: + SUITE_NAME: opentracer +protobuf: runner: riot is_snapshot: true - services: - - httpbin_local env: - SUITE_NAME: requests -sanic: + SUITE_NAME: protobuf +pylibmc: runner: riot is_snapshot: true + services: + - memcached env: - SUITE_NAME: sanic -snowflake: + SUITE_NAME: pylibmc +pymemcache: runner: riot is_snapshot: true + services: + - memcached env: - SUITE_NAME: snowflake -starlette: + SUITE_NAME: pymemcache +pymongo: runner: riot is_snapshot: true + services: + - mongo env: - SUITE_NAME: starlette -structlog: + SUITE_NAME: pymongo +pynamodb: runner: riot is_snapshot: true env: - SUITE_NAME: structlog -aredis: + SUITE_NAME: pynamodb +pyodbc: runner: riot is_snapshot: true - services: - - redis env: - SUITE_NAME: "aredis$" - parallelism: 3 -yaaredis: + SUITE_NAME: pyodbc +pyramid: runner: riot is_snapshot: true - services: - - redis env: - SUITE_NAME: "yaaredis$" + SUITE_NAME: pyramid redis: runner: riot is_snapshot: true @@ -262,99 +279,82 @@ rediscluster: - rediscluster env: SUITE_NAME: rediscluster -rq: - runner: riot - is_snapshot: true - services: - - redis - env: - SUITE_NAME: rq - parallelism: 2 -urllib3: +requests: runner: riot is_snapshot: true services: - httpbin_local env: - SUITE_NAME: urllib3 - TEST_HTTPBIN_HOST: "httpbin-local" - TEST_HTTPBIN_PORT: "8001" -wsgi: - runner: riot - is_snapshot: true - env: - SUITE_NAME: wsgi -kafka: + SUITE_NAME: requests +rq: runner: riot is_snapshot: true services: - - kafka + - redis env: - SUITE_NAME: kafka - TEST_KAFKA_HOST: "kafka" - TEST_KAFKA_PORT: "29092" - parallelism: 4 -kombu: + SUITE_NAME: rq + parallelism: 2 +sanic: runner: riot is_snapshot: true - services: - - rabbitmq env: - SUITE_NAME: kombu -jinja2: + SUITE_NAME: sanic +snowflake: runner: riot is_snapshot: true env: - SUITE_NAME: jinja2 -mako: + SUITE_NAME: snowflake +sourcecode: runner: riot - is_snapshot: true env: - SUITE_NAME: mako -algoliasearch: + SUITE_NAME: sourcecode +starlette: runner: riot is_snapshot: true env: - SUITE_NAME: algoliasearch -logbook: + SUITE_NAME: starlette +stdlib: runner: riot is_snapshot: true env: - SUITE_NAME: logbook -loguru: + SUITE_NAME: 'asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$' +structlog: runner: riot is_snapshot: true env: - SUITE_NAME: loguru -stdlib: + SUITE_NAME: structlog +subprocess: runner: riot - is_snapshot: true env: - SUITE_NAME: 'asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$' + SUITE_NAME: subprocess test_logging: runner: riot is_snapshot: true env: SUITE_NAME: test_logging -subprocess: - runner: riot - env: - SUITE_NAME: subprocess -sourcecode: +tornado: runner: riot + is_snapshot: true env: - SUITE_NAME: sourcecode -opentracer: + SUITE_NAME: tornado +urllib3: runner: riot + is_snapshot: true + services: + - httpbin_local env: - SUITE_NAME: opentracer -protobuf: + SUITE_NAME: urllib3 + TEST_HTTPBIN_HOST: "httpbin-local" + TEST_HTTPBIN_PORT: "8001" +wsgi: runner: riot is_snapshot: true env: - SUITE_NAME: protobuf -avro: + SUITE_NAME: wsgi +yaaredis: runner: riot is_snapshot: true + services: + - redis env: - SUITE_NAME: avro + SUITE_NAME: "yaaredis$" \ No newline at end of file From 7297779d1553b616e42bdc34279ba97eba4258de Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:17:14 -0500 Subject: [PATCH 182/372] chore(tracing): fix inferred base service for pytest command ran with --ddtrace optional argument (#11394) # Motivation Add special case for `--ddtrace` pytest argument, that was causing CI tests to have a different service name than local testing. CI testing adds the `--ddtrace` arg to the command. Previously the code would skip any args that started with `-` as well as the following arg, but that should not be the case for this arg. --- ddtrace/contrib/internal/mako/patch.py | 7 +- ddtrace/sampler.py | 11 +- ddtrace/settings/_inferred_base_service.py | 4 +- ddtrace/settings/config.py | 7 +- ...erred-service-naming-5ba12e991d040a1b.yaml | 6 + .../anthropic/test_anthropic_llmobs.py | 28 +- tests/contrib/django/test_django.py | 4 +- tests/contrib/flask/test_blueprint.py | 4 +- tests/contrib/flask/test_errorhandler.py | 2 +- tests/contrib/flask/test_flask_helpers.py | 2 +- tests/contrib/flask/test_template.py | 4 +- tests/contrib/flask/test_views.py | 2 +- .../test_google_generativeai_llmobs.py | 36 +-- tests/contrib/httplib/test_httplib.py | 22 +- tests/contrib/httpx/test_httpx.py | 2 +- tests/contrib/httpx/test_httpx_pre_0_11.py | 2 +- tests/contrib/jinja2/test_jinja2.py | 6 +- tests/contrib/langchain/test_langchain.py | 34 +-- .../langchain/test_langchain_community.py | 30 +-- .../langchain/test_langchain_llmobs.py | 24 +- tests/contrib/logging/test_logging.py | 20 +- tests/contrib/openai/test_openai_llmobs.py | 58 ++--- tests/contrib/openai/test_openai_v0.py | 20 +- tests/contrib/openai/test_openai_v1.py | 22 +- tests/debugging/test_config.py | 3 +- tests/integration/test_debug.py | 2 +- .../test_remoteconfig_client_e2e.py | 2 +- .../test_inferred_base_service.py | 11 + tests/llmobs/_utils.py | 24 +- tests/llmobs/test_llmobs_span_agent_writer.py | 6 +- .../test_llmobs_span_agentless_writer.py | 6 +- tests/llmobs/test_llmobs_trace_processor.py | 4 +- ...pling_float_special_case_do_not_match.json | 2 +- ...ampling_float_special_case_match_star.json | 2 +- ...est_extended_sampling_glob_multi_rule.json | 2 +- .../test_extended_sampling_resource.json | 4 +- .../test_extended_sampling_tags.json | 4 +- ..._extended_sampling_tags_and_name_glob.json | 8 +- ...t_extended_sampling_tags_and_resource.json | 8 +- ...ended_sampling_tags_and_resource_glob.json | 8 +- ...tended_sampling_tags_and_service_glob.json | 8 +- .../test_extended_sampling_tags_glob.json | 4 +- ...ling_tags_glob_insensitive_case_match.json | 4 +- .../test_extended_sampling_w_None.json | 6 +- .../test_extended_sampling_w_None_meta.json | 6 +- .../test_extended_sampling_w_metrics.json | 6 +- .../test_extended_sampling_w_tags_none.json | 6 +- ...t_sampling_with_default_sample_rate_1.json | 4 +- ...default_sample_rate_1_and_manual_drop.json | 4 +- ...default_sample_rate_1_and_manual_keep.json | 4 +- ...with_default_sample_rate_1_and_rule_0.json | 4 +- ...with_default_sample_rate_1_and_rule_1.json | 4 +- ...ampling_with_default_sample_rate_tiny.json | 4 +- .../test_sampling_with_defaults.json | 4 +- .../test_sampling_with_rate_limit_3.json | 4 +- ...ling_with_rate_sampler_with_tiny_rate.json | 4 +- ...g_with_sample_rate_1_and_rate_limit_0.json | 4 +- ...le_rate_1_and_rate_limit_3_and_rule_0.json | 4 +- ...st_appsec_body_no_collection_snapshot.json | 2 +- ...appsec_cookies_no_collection_snapshot.json | 2 +- ...cessor.test_appsec_span_tags_snapshot.json | 2 +- ...appsec_span_tags_snapshot_with_errors.json | 2 +- ....test_aiohttp_client.test_200_request.json | 4 +- ..._aiohttp_client.test_200_request_post.json | 4 +- ....test_aiohttp_client.test_500_request.json | 4 +- ..._aiohttp_client.test_auth_200_request.json | 4 +- ...onfigure_service_name_split_by_domain.json | 4 +- ...st_aiohttp_client.test_trace_multiple.json | 12 +- ...t_aiohttp_client.test_trace_parenting.json | 6 +- ...iohttp_client.test_trace_query_string.json | 4 +- ...nja2.test_template_rendering_snapshot.json | 2 +- ...t_template_rendering_snapshot[pyloop].json | 2 +- ...ndering_snapshot_patched_server[True].json | 4 +- ...pic.test_anthropic.test_anthropic_llm.json | 2 +- ...ropic.test_anthropic_llm_create_image.json | 2 +- ...st_anthropic.test_anthropic_llm_error.json | 2 +- ...c.test_anthropic_llm_multiple_prompts.json | 2 +- ...lm_multiple_prompts_with_chat_history.json | 2 +- ...t_anthropic.test_anthropic_llm_stream.json | 2 +- ...opic.test_anthropic_llm_stream_helper.json | 2 +- ...ropic.test_anthropic_llm_stream_image.json | 2 +- ...st_anthropic.test_anthropic_llm_tools.json | 2 +- ...pic.test_anthropic_llm_tools_full_use.json | 4 +- ...t_anthropic_llm_tools_full_use_stream.json | 4 +- ...ropic.test_anthropic_llm_tools_stream.json | 2 +- ...est_anthropic_llm_tools_stream_helper.json | 2 +- ...test_anthropic_llm_unserializable_arg.json | 2 +- ....test_aredis.test_analytics_with_rate.json | 2 +- ...st_aredis.test_analytics_without_rate.json | 2 +- ...ontrib.aredis.test_aredis.test_basics.json | 2 +- ...redis.test_aredis.test_cmd_max_length.json | 2 +- ...full_command_in_resource_config[True].json | 6 +- ....aredis.test_aredis.test_long_command.json | 2 +- ...b.aredis.test_aredis.test_opentracing.json | 4 +- ...s.test_aredis.test_pipeline_immediate.json | 4 +- ...edis.test_aredis.test_pipeline_traced.json | 2 +- ...ntrib.aredis.test_aredis.test_unicode.json | 2 +- ..._django_snapshots.test_404_exceptions.json | 62 ++--- ...s.test_middleware_trace_callable_view.json | 52 ++-- ...o_snapshots.test_safe_string_encoding.json | 66 ++--- ...t_django_snapshots.test_streamed_file.json | 52 ++-- ...test_django.test_django_hosts_request.json | 52 ++-- ...egration.test_fn_exception_no_retries.json | 2 +- ...t_integration.test_fn_retry_exception.json | 2 +- ...iq.test_integration.test_fn_task_send.json | 4 +- ...gration.test_fn_task_send_with_params.json | 4 +- ...est_integration.test_idempotent_patch.json | 4 +- ....test_integration.test_send_exception.json | 2 +- ...pi.test_fastapi.test_500_error_raised.json | 2 +- ...api.test_fastapi.test_background_task.json | 6 +- ...api.test_fastapi.test_subapp_snapshot.json | 6 +- ...est_fastapi.test_table_query_snapshot.json | 8 +- ...pi.test_fastapi.test_traced_websocket.json | 2 +- ...st_fastapi.test_tracing_in_middleware.json | 6 +- ...e_generativeai.test_gemini_completion.json | 2 +- ...rativeai.test_gemini_completion_error.json | 2 +- ...rativeai.test_gemini_completion_image.json | 2 +- ...t_gemini_completion_multiple_messages.json | 2 +- ...ativeai.test_gemini_completion_stream.json | 2 +- ....test_gemini_completion_system_prompt.json | 2 +- ...ai.test_gemini_completion_tool_stream.json | 2 +- ...veai.test_gemini_tool_chat_completion.json | 4 +- ...erativeai.test_gemini_tool_completion.json | 2 +- ...ene.test_graphene.test_schema_execute.json | 8 +- ...ne.test_schema_execute_with_resolvers.json | 16 +- ..._graphene.test_schema_failing_execute.json | 10 +- ...rib.graphql.test_graphql.test_graphql.json | 8 +- ...aphql.test_graphql.test_graphql_error.json | 6 +- ....test_graphql_execute_with_middleware.json | 16 +- ...raphql_with_document_with_no_location.json | 4 +- ...hql.test_graphql_with_traced_resolver.json | 10 +- ...est_httpx.test_configure_service_name.json | 2 +- ...contrib.httpx.test_httpx.test_get_200.json | 2 +- ...contrib.httpx.test_httpx.test_get_500.json | 2 +- ...httpx.test_httpx.test_request_headers.json | 2 +- ..._unspecified_service_name_env_default.json | 2 +- ...tized_unspecified_service_name_env_v0.json | 2 +- ...httpx.test_httpx.test_split_by_domain.json | 2 +- ...px.test_httpx.test_trace_query_string.json | 2 +- ...ain.test_langchain.test_ai21_llm_sync.json | 2 +- ...n.test_langchain.test_cohere_llm_sync.json | 2 +- ...test_langchain.test_cohere_math_chain.json | 6 +- ...angchain.test_fake_embedding_document.json | 2 +- ...t_langchain.test_fake_embedding_query.json | 2 +- ...angchain.test_huggingfacehub_llm_sync.json | 2 +- ...langchain.test_openai_chat_model_call.json | 2 +- ...chain.test_openai_chat_model_generate.json | 2 +- ...n.test_openai_chat_model_sync_call_39.json | 2 +- ...chat_model_sync_call_langchain_openai.json | 2 +- ...st_openai_chat_model_sync_generate_39.json | 2 +- ...gchain.test_openai_embedding_document.json | 2 +- ...langchain.test_openai_embedding_query.json | 2 +- ....test_langchain.test_openai_llm_async.json | 2 +- ....test_langchain.test_openai_llm_error.json | 2 +- ...n.test_langchain.test_openai_llm_sync.json | 2 +- ...est_langchain.test_openai_llm_sync_39.json | 2 +- ...test_openai_llm_sync_multiple_prompts.json | 2 +- ...t_openai_llm_sync_multiple_prompts_39.json | 2 +- ...test_langchain.test_openai_math_chain.json | 6 +- ...angchain.test_openai_sequential_chain.json | 8 +- ...uential_chain_with_multiple_llm_async.json | 10 +- ...quential_chain_with_multiple_llm_sync.json | 10 +- ..._pinecone_vectorstore_retrieval_chain.json | 12 +- ...necone_vectorstore_retrieval_chain_39.json | 12 +- ...inecone_vectorstore_similarity_search.json | 4 +- ...angchain_community.test_ai21_llm_sync.json | 2 +- ...chain_community.test_base_tool_invoke.json | 2 +- ...l_invoke_non_json_serializable_config.json | 2 +- ...langchain_community.test_chain_invoke.json | 4 +- ...gchain_community.test_cohere_llm_sync.json | 2 +- ...nity.test_faiss_vectorstore_retrieval.json | 6 +- ...ommunity.test_fake_embedding_document.json | 2 +- ...n_community.test_fake_embedding_query.json | 2 +- ...ommunity.test_huggingfacehub_llm_sync.json | 2 +- ...unity.test_lcecl_chain_non_dict_input.json | 2 +- ...chain_community.test_lcel_chain_batch.json | 6 +- ...n_community.test_lcel_chain_batch_311.json | 6 +- ...community.test_lcel_chain_batch_async.json | 6 +- ...community.test_lcel_chain_complicated.json | 4 +- ...hain_community.test_lcel_chain_nested.json | 8 +- ...munity.test_lcel_chain_non_dict_input.json | 2 +- ...hain_community.test_lcel_chain_simple.json | 4 +- ...ommunity.test_lcel_chain_simple_async.json | 4 +- ...munity.test_lcel_with_tools_anthropic.json | 2 +- ...community.test_lcel_with_tools_openai.json | 2 +- ...ity.test_openai_chat_model_async_call.json | 2 +- ...test_openai_chat_model_async_generate.json | 2 +- ...chat_model_sync_call_langchain_openai.json | 2 +- ....test_openai_chat_model_sync_generate.json | 2 +- ...est_openai_chat_model_vision_generate.json | 2 +- ...community.test_openai_embedding_query.json | 2 +- ...chain_community.test_openai_llm_async.json | 2 +- ...chain_community.test_openai_llm_error.json | 2 +- ...gchain_community.test_openai_llm_sync.json | 2 +- ...test_openai_llm_sync_multiple_prompts.json | 2 +- ...hain_community.test_openai_math_chain.json | 6 +- ...ommunity.test_openai_math_chain_async.json | 6 +- ...ommunity.test_openai_sequential_chain.json | 8 +- ...uential_chain_with_multiple_llm_async.json | 10 +- ...quential_chain_with_multiple_llm_sync.json | 10 +- ..._pinecone_vectorstore_retrieval_chain.json | 12 +- ...inecone_vectorstore_similarity_search.json | 4 +- ...ngchain_community.test_streamed_chain.json | 4 +- ...angchain_community.test_streamed_chat.json | 2 +- ...nity.test_streamed_json_output_parser.json | 4 +- ...langchain_community.test_streamed_llm.json | 2 +- ...b.openai.test_openai.test_acompletion.json | 2 +- ...b.openai.test_openai.test_atranscribe.json | 2 +- ...ib.openai.test_openai.test_atranslate.json | 2 +- ...nai.test_azure_openai_chat_completion.json | 2 +- ...t_openai.test_azure_openai_completion.json | 2 +- ...st_openai.test_azure_openai_embedding.json | 2 +- ...enai.test_openai.test_chat_completion.json | 2 +- ...test_chat_completion_function_calling.json | 2 +- ...enai.test_chat_completion_image_input.json | 2 +- ...ib.openai.test_openai.test_completion.json | 2 +- ...ai.test_openai.test_create_moderation.json | 2 +- ...rib.openai.test_openai.test_embedding.json | 2 +- ....test_embedding_array_of_token_arrays.json | 2 +- ...st_openai.test_embedding_string_array.json | 2 +- ...est_openai.test_embedding_token_array.json | 2 +- ...b.openai.test_openai.test_file_create.json | 2 +- ...b.openai.test_openai.test_file_delete.json | 2 +- ...openai.test_openai.test_file_download.json | 2 +- ...rib.openai.test_openai.test_file_list.json | 2 +- ...openai.test_openai.test_file_retrieve.json | 2 +- ...t_openai.test_image_b64_json_response.json | 2 +- ....openai.test_openai.test_image_create.json | 2 +- ...ib.openai.test_openai.test_image_edit.json | 2 +- ...t_openai.test_image_edit_binary_input.json | 2 +- ...enai.test_openai.test_image_variation.json | 2 +- ...ontrib.openai.test_openai.test_misuse.json | 2 +- ....openai.test_openai.test_model_delete.json | 2 +- ...ib.openai.test_openai.test_model_list.json | 2 +- ...penai.test_openai.test_model_retrieve.json | 2 +- ...enai.test_span_finish_on_stream_error.json | 2 +- ...ib.openai.test_openai.test_transcribe.json | 2 +- ...rib.openai.test_openai.test_translate.json | 2 +- ...s.test_redis.test_analytics_with_rate.json | 2 +- ...est_redis.test_analytics_without_rate.json | 2 +- ....contrib.redis.test_redis.test_basics.json | 2 +- ...dis.test_redis.test_custom_cmd_length.json | 2 +- ....test_full_command_in_resource_config.json | 4 +- ...ib.redis.test_redis.test_long_command.json | 2 +- ...b.redis.test_redis.test_meta_override.json | 2 +- ...rib.redis.test_redis.test_opentracing.json | 4 +- ...is.test_redis.test_pipeline_immediate.json | 4 +- ...redis.test_redis.test_pipeline_traced.json | 2 +- ...contrib.redis.test_redis.test_unicode.json | 2 +- ...test_redis_asyncio.test_basic_request.json | 2 +- ...s.test_redis_asyncio.test_client_name.json | 4 +- ...t_redis_asyncio.test_connection_error.json | 2 +- ...s_asyncio.test_decoding_non_utf8_args.json | 4 +- ....test_decoding_non_utf8_pipeline_args.json | 2 +- ....test_redis_asyncio.test_long_command.json | 2 +- ...is_asyncio.test_override_service_name.json | 6 +- ...dis.test_redis_asyncio.test_parenting.json | 6 +- ...rib.redis.test_redis_asyncio.test_pin.json | 2 +- ...st_redis_asyncio.test_pipeline_traced.json | 2 +- ...ne_traced_context_manager_transaction.json | 2 +- ...dis_asyncio.test_two_traced_pipelines.json | 6 +- ...st_redis_asyncio.test_unicode_request.json | 2 +- ...rediscluster.test.test_cmd_max_length.json | 4 +- ...full_command_in_resource_config[True].json | 8 +- ...ing_enabled_False_worker_service_None.json | 2 +- ..._worker_service_custom-worker-service.json | 2 +- ...cing_enabled_None_worker_service_None.json | 2 +- ..._worker_service_custom-worker-service.json | 2 +- ...rib.rq.test_rq.test_queue_failing_job.json | 4 +- ..._rq.test_queue_failing_job_pre_1_10_1.json | 4 +- ...rib.rq.test_rq.test_queue_pin_service.json | 6 +- ...ib.rq.test_rq.test_sync_queue_enqueue.json | 4 +- ...s.contrib.rq.test_rq.test_sync_worker.json | 6 +- ...st_rq.test_sync_worker_config_service.json | 6 +- ...est_rq.test_sync_worker_multiple_jobs.json | 18 +- ....test_rq.test_sync_worker_pin_service.json | 6 +- ...ntrib.rq.test_rq.test_sync_worker_ttl.json | 6 +- ...trib.rq.test_rq.test_worker_class_job.json | 12 +- ...ib.rq.test_rq.test_worker_failing_job.json | 6 +- ....test_snowflake.test_snowflake_commit.json | 2 +- ...ake.test_snowflake_executemany_insert.json | 2 +- ...est_snowflake.test_snowflake_fetchall.json | 2 +- ...test_snowflake_fetchall_multiple_rows.json | 2 +- ...est_snowflake.test_snowflake_fetchone.json | 2 +- ....test_snowflake_ot_executemany_insert.json | 4 +- ..._snowflake.test_snowflake_ot_fetchall.json | 4 +- ...t_snowflake_ot_fetchall_multiple_rows.json | 4 +- ..._snowflake.test_snowflake_ot_fetchone.json | 4 +- ...snowflake.test_snowflake_pin_override.json | 2 +- ...est_snowflake.test_snowflake_rollback.json | 2 +- ...lake.test_snowflake_settings_override.json | 2 +- ...e.test_starlette.test_background_task.json | 6 +- ...ette.test_subapp_nested_call_snapshot.json | 4 +- ...starlette.test_subapp_nested_snapshot.json | 6 +- ...e.test_starlette.test_subapp_snapshot.json | 4 +- ...st_starlette.test_subapp_two_snapshot.json | 4 +- ...t_starlette.test_table_query_snapshot.json | 8 +- ....test_urllib3_connectionpool_snapshot.json | 2 +- ...ib3.test_urllib3_poolmanager_snapshot.json | 2 +- ...tests.contrib.wsgi.test_wsgi.test_200.json | 8 +- ...s.contrib.wsgi.test_wsgi.test_500_py3.json | 4 +- ...i.test_base_exception_in_wsgi_app_py3.json | 4 +- ...s.contrib.wsgi.test_wsgi.test_chunked.json | 8 +- ..._wsgi.test_distributed_tracing_nested.json | 16 +- ....test_generator_exit_ignored_snapshot.json | 8 +- ...i.test_stop_iteration_in_wsgi_app_py3.json | 8 +- ...i.test_wsgi.test_wsgi_base_middleware.json | 6 +- ...st_wsgi.test_wsgi_base_middleware_500.json | 4 +- ...est_yaaredis.test_analytics_with_rate.json | 2 +- ..._yaaredis.test_analytics_without_rate.json | 2 +- ...ib.yaaredis.test_yaaredis.test_basics.json | 2 +- ...dis.test_yaaredis.test_cmd_max_length.json | 2 +- ...full_command_in_resource_config[True].json | 6 +- ...redis.test_yaaredis.test_long_command.json | 2 +- ...aredis.test_yaaredis.test_opentracing.json | 4 +- ...test_yaaredis.test_pipeline_immediate.json | 4 +- ...is.test_yaaredis.test_pipeline_traced.json | 2 +- ...b.yaaredis.test_yaaredis.test_unicode.json | 2 +- ...st_integration_snapshots.test_filters.json | 4 +- ...ration_snapshots.test_multiple_traces.json | 8 +- ...t_integration_snapshots.test_sampling.json | 28 +- ...metrics_generates_no_error_logs[v0.4].json | 2 +- ...metrics_generates_no_error_logs[v0.5].json | 2 +- ...apshots.test_single_trace_single_span.json | 2 +- ...ion_snapshots.test_synchronous_writer.json | 8 +- ...ace_with_wrong_metrics_types_not_sent.json | 2 +- ...t_tracer_trace_across_multiple_popens.json | 6 +- ...pshots.test_tracer_trace_across_popen.json | 4 +- ...tracetagsprocessor_only_adds_new_tags.json | 2 +- ..._sampling.test_agent_sample_rate_keep.json | 4 +- ...ampling.test_agent_sample_rate_reject.json | 4 +- ...ion.test_sampling_decision_downstream.json | 2 +- ...on.test_trace_tags_multispan[tracer0].json | 8 +- ...on.test_trace_tags_multispan[tracer1].json | 8 +- ...on.test_trace_tags_multispan[tracer2].json | 8 +- ...n.test_trace_stats.test_measured_span.json | 80 +++--- ...t_trace_stats.test_sampling_rate[0.0].json | 20 +- ...t_trace_stats.test_sampling_rate[1.0].json | 20 +- ..._single_span_sampling[sampling_rule0].json | 2 +- ..._single_span_sampling[sampling_rule1].json | 4 +- ...ration.test_trace_stats.test_stats_30.json | 60 ++--- ...ion.test_trace_stats.test_stats_aggrs.json | 14 +- ...on.test_trace_stats.test_stats_errors.json | 60 ++--- ...ation.test_trace_stats.test_top_level.json | 120 ++++----- tests/tracer/runtime/test_runtime_metrics.py | 2 +- tests/tracer/test_correlation_log_context.py | 244 +++++++++++------- tests/tracer/test_sampler.py | 124 ++++----- tests/tracer/test_settings.py | 2 +- tests/tracer/test_trace_utils.py | 9 +- tests/tracer/test_tracer.py | 11 +- 350 files changed, 1360 insertions(+), 1271 deletions(-) create mode 100644 releasenotes/notes/fix-pytest-inferred-service-naming-5ba12e991d040a1b.yaml diff --git a/ddtrace/contrib/internal/mako/patch.py b/ddtrace/contrib/internal/mako/patch.py index 6c9c11dc8f2..7db3b2e47df 100644 --- a/ddtrace/contrib/internal/mako/patch.py +++ b/ddtrace/contrib/internal/mako/patch.py @@ -4,6 +4,7 @@ from ddtrace import config from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.trace_utils import int_service from ddtrace.contrib.trace_utils import unwrap as _u from ddtrace.contrib.trace_utils import wrap as _w from ddtrace.ext import SpanTypes @@ -26,7 +27,7 @@ def patch(): return mako.__datadog_patch = True - Pin(service=config.service or schematize_service_name("mako")).onto(Template) + Pin().onto(Template) _w(mako, "template.Template.render", _wrap_render) _w(mako, "template.Template.render_unicode", _wrap_render) @@ -57,7 +58,9 @@ def _wrap_render(wrapped, instance, args, kwargs): template_name = getattr(instance, "filename", None) template_name = template_name or DEFAULT_TEMPLATE_NAME - with pin.tracer.trace(func_name(wrapped), pin.service, span_type=SpanTypes.TEMPLATE) as span: + with pin.tracer.trace( + func_name(wrapped), int_service(pin, config.mako, schematize_service_name("mako")), span_type=SpanTypes.TEMPLATE + ) as span: span.set_tag_str(COMPONENT, "mako") span.set_tag(SPAN_MEASURED_KEY) diff --git a/ddtrace/sampler.py b/ddtrace/sampler.py index 9c78905a70b..be6f8898ef0 100644 --- a/ddtrace/sampler.py +++ b/ddtrace/sampler.py @@ -134,10 +134,17 @@ def __init__(self, sample_rate=1.0): def set_sample_rate( self, sample_rate, # type: float - service="", # type: str - env="", # type: str + service=None, # type: Optional[str] + env=None, # type: Optional[str] ): # type: (...) -> None + + # if we have a blank service, we need to match it to the config.service + if service is None: + service = config.service + if env is None: + env = config.env + self._by_service_samplers[self._key(service, env)] = _AgentRateSampler(sample_rate) def sample(self, span): diff --git a/ddtrace/settings/_inferred_base_service.py b/ddtrace/settings/_inferred_base_service.py index 091c5b5d2e9..a7d01c09dd8 100644 --- a/ddtrace/settings/_inferred_base_service.py +++ b/ddtrace/settings/_inferred_base_service.py @@ -54,7 +54,9 @@ def detect(self, args: List[str]) -> Optional[ServiceMetadata]: module_flag = False for arg in args: - has_flag_prefix = arg.startswith("-") + # we support the --ddtrace option for pytest, and shouldn't skip the following arg + # since it's usually the test location argument. + has_flag_prefix = arg.startswith("-") and not arg.startswith("--ddtrace") is_env_variable = "=" in arg should_skip_arg = prev_arg_is_flag or has_flag_prefix or is_env_variable diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 8af5aa9b2d6..65baf99ccb3 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -474,15 +474,16 @@ def __init__(self): self._propagation_http_baggage_enabled = _get_config("DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED", False, asbool) self.env = _get_config("DD_ENV", self.tags.get("env")) - self.service = _get_config("DD_SERVICE", self.tags.get("service", DEFAULT_SPAN_SERVICE_NAME)) + self.service = _get_config("DD_SERVICE", self.tags.get("service", None)) + self._inferred_base_service = detect_service(sys.argv) if self.service is None and in_gcp_function(): self.service = _get_config(["K_SERVICE", "FUNCTION_NAME"], DEFAULT_SPAN_SERVICE_NAME) if self.service is None and in_azure_function(): self.service = _get_config("WEBSITE_SITE_NAME", DEFAULT_SPAN_SERVICE_NAME) - if self.service is None and self._inferred_base_service: - self.service = self._inferred_base_service + if self.service is None and DEFAULT_SPAN_SERVICE_NAME: + self.service = _get_config("DD_SERVICE", DEFAULT_SPAN_SERVICE_NAME) self._extra_services = set() self._extra_services_queue = None if in_aws_lambda() or not self._remote_config_enabled else File_Queue() diff --git a/releasenotes/notes/fix-pytest-inferred-service-naming-5ba12e991d040a1b.yaml b/releasenotes/notes/fix-pytest-inferred-service-naming-5ba12e991d040a1b.yaml new file mode 100644 index 00000000000..cb6977a3816 --- /dev/null +++ b/releasenotes/notes/fix-pytest-inferred-service-naming-5ba12e991d040a1b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + ci_visibility: Updates the inferred base service name algorithm to ensure that arguments following ``--ddtrace`` are no + longer skipped when executing tests with pytest. Previously, the algorithm misinterpreted these arguments + as standard flags, overlooking possible test paths that may contribute to the inferred service name. diff --git a/tests/contrib/anthropic/test_anthropic_llmobs.py b/tests/contrib/anthropic/test_anthropic_llmobs.py index f39641b0811..f286a890209 100644 --- a/tests/contrib/anthropic/test_anthropic_llmobs.py +++ b/tests/contrib/anthropic/test_anthropic_llmobs.py @@ -68,7 +68,7 @@ def test_completion(self, anthropic, ddtrace_global_config, mock_llmobs_writer, output_messages=[{"content": 'THE BEST-SELLING BOOK OF ALL TIME IS "DON', "role": "assistant"}], metadata={"temperature": 0.8, "max_tokens": 15.0}, token_metrics={"input_tokens": 32, "output_tokens": 15, "total_tokens": 47}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -113,7 +113,7 @@ def test_error(self, anthropic, ddtrace_global_config, mock_llmobs_writer, mock_ error_message=span.get_tag("error.message"), error_stack=span.get_tag("error.stack"), metadata={"temperature": 0.8, "max_tokens": 15.0}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -142,7 +142,7 @@ def test_error_unserializable_arg( error_message=span.get_tag("error.message"), error_stack=span.get_tag("error.stack"), metadata={"temperature": 0.8, "max_tokens": mock.ANY}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_span) actual_span = mock_llmobs_writer.enqueue.call_args[0][0] @@ -196,7 +196,7 @@ def test_stream(self, anthropic, ddtrace_global_config, mock_llmobs_writer, mock ], metadata={"temperature": 0.8, "max_tokens": 15.0}, token_metrics={"input_tokens": 27, "output_tokens": 15, "total_tokens": 42}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -253,7 +253,7 @@ def test_stream_helper(self, anthropic, ddtrace_global_config, mock_llmobs_write ], metadata={"temperature": 0.8, "max_tokens": 15.0}, token_metrics={"input_tokens": 27, "output_tokens": 15, "total_tokens": 42}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -308,7 +308,7 @@ def test_image(self, anthropic, ddtrace_global_config, mock_llmobs_writer, mock_ ], metadata={"temperature": 0.8, "max_tokens": 15.0}, token_metrics={"input_tokens": 246, "output_tokens": 15, "total_tokens": 261}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -350,7 +350,7 @@ def test_tools_sync(self, anthropic, ddtrace_global_config, mock_llmobs_writer, ], metadata={"max_tokens": 200.0}, token_metrics={"input_tokens": 599, "output_tokens": 152, "total_tokens": 751}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -403,7 +403,7 @@ def test_tools_sync(self, anthropic, ddtrace_global_config, mock_llmobs_writer, ], metadata={"max_tokens": 500.0}, token_metrics={"input_tokens": 768, "output_tokens": 29, "total_tokens": 797}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -446,7 +446,7 @@ async def test_tools_async(self, anthropic, ddtrace_global_config, mock_llmobs_w ], metadata={"max_tokens": 200.0}, token_metrics={"input_tokens": 599, "output_tokens": 152, "total_tokens": 751}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -499,7 +499,7 @@ async def test_tools_async(self, anthropic, ddtrace_global_config, mock_llmobs_w ], metadata={"max_tokens": 500.0}, token_metrics={"input_tokens": 768, "output_tokens": 29, "total_tokens": 797}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -559,7 +559,7 @@ def test_tools_sync_stream(self, anthropic, ddtrace_global_config, mock_llmobs_w ], metadata={"max_tokens": 200.0}, token_metrics={"input_tokens": 599, "output_tokens": 135, "total_tokens": 734}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -609,7 +609,7 @@ def test_tools_sync_stream(self, anthropic, ddtrace_global_config, mock_llmobs_w ], metadata={"max_tokens": 500.0}, token_metrics={"input_tokens": 762, "output_tokens": 33, "total_tokens": 795}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -664,7 +664,7 @@ async def test_tools_async_stream_helper( ], metadata={"max_tokens": 200.0}, token_metrics={"input_tokens": 599, "output_tokens": 146, "total_tokens": 745}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) @@ -719,6 +719,6 @@ async def test_tools_async_stream_helper( ], metadata={"max_tokens": 500.0}, token_metrics={"input_tokens": 762, "output_tokens": 18, "total_tokens": 780}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.anthropic"}, ) ) diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index 04a04268099..cf44ed6bcdd 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -1425,14 +1425,14 @@ def test_cached_view(client, test_spans): "django.cache.key": ( "views.decorators.cache.cache_page..GET.03cdc1cc4aab71b038a6764e5fcabb82.d41d8cd98f00b204e9800998ecf8..." ), - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", } expected_meta_header = { "component": "django", "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", "django.cache.key": "views.decorators.cache.cache_header..03cdc1cc4aab71b038a6764e5fcabb82.en-us", - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", } assert span_view.get_tags() == expected_meta_view diff --git a/tests/contrib/flask/test_blueprint.py b/tests/contrib/flask/test_blueprint.py index efc18ae54e9..93b6c2d7ffc 100644 --- a/tests/contrib/flask/test_blueprint.py +++ b/tests/contrib/flask/test_blueprint.py @@ -104,7 +104,7 @@ def test(): self.assertEqual(span.name, "bp.test") self.assertEqual(span.resource, "/") self.assertNotEqual(span.parent_id, 0) - self.assertEqual(span.get_tags(), {"component": "flask", "_dd.base_service": ""}) + self.assertEqual(span.get_tags(), {"component": "flask", "_dd.base_service": "tests.contrib.flask"}) def test_blueprint_request_pin_override(self): """ @@ -132,7 +132,7 @@ def test(): self.assertEqual(span.name, "bp.test") self.assertEqual(span.resource, "/") self.assertNotEqual(span.parent_id, 0) - self.assertEqual(span.get_tags(), {"component": "flask", "_dd.base_service": ""}) + self.assertEqual(span.get_tags(), {"component": "flask", "_dd.base_service": "tests.contrib.flask"}) def test_blueprint_request_pin_disabled(self): """ diff --git a/tests/contrib/flask/test_errorhandler.py b/tests/contrib/flask/test_errorhandler.py index a966ca9396e..8a2a91019e5 100644 --- a/tests/contrib/flask/test_errorhandler.py +++ b/tests/contrib/flask/test_errorhandler.py @@ -6,7 +6,7 @@ from . import BaseFlaskTestCase -EXPECTED_METADATA = {"component": "flask", "_dd.base_service": ""} +EXPECTED_METADATA = {"component": "flask", "_dd.base_service": "tests.contrib.flask"} class FlaskErrorhandlerTestCase(BaseFlaskTestCase): diff --git a/tests/contrib/flask/test_flask_helpers.py b/tests/contrib/flask/test_flask_helpers.py index 28a1a52f36f..73f6fb1bf98 100644 --- a/tests/contrib/flask/test_flask_helpers.py +++ b/tests/contrib/flask/test_flask_helpers.py @@ -49,7 +49,7 @@ def test_jsonify(self): spans = self.get_spans() self.assertEqual(len(spans), 3) - self.assertIsNone(spans[0].service) + self.assertEqual(spans[0].service, "tests.contrib.flask") self.assertEqual(spans[0].name, "flask.jsonify") self.assertEqual(spans[0].resource, "flask.jsonify") assert set(spans[0].get_tags().keys()) == {"runtime-id", "_dd.p.dm", "_dd.p.tid", "component", "language"} diff --git a/tests/contrib/flask/test_template.py b/tests/contrib/flask/test_template.py index 90cd4cb7f97..f15c67aa207 100644 --- a/tests/contrib/flask/test_template.py +++ b/tests/contrib/flask/test_template.py @@ -48,7 +48,7 @@ def test_render_template(self): spans = self.get_spans() self.assertEqual(len(spans), 3) - self.assertIsNone(spans[0].service) + self.assertEqual(spans[0].service, "tests.contrib.flask") self.assertEqual(spans[0].name, "flask.render_template") resource = "tests.contrib.flask" if flask_version >= (2, 2, 0) else "test.html" self.assertEqual(spans[0].resource, resource) # FIXME: should always be 'test.html'? @@ -92,7 +92,7 @@ def test_render_template_string(self): spans = self.get_spans() self.assertEqual(len(spans), 3) - self.assertIsNone(spans[0].service) + self.assertEqual(spans[0].service, "tests.contrib.flask") self.assertEqual(spans[0].name, "flask.render_template_string") resource = "tests.contrib.flask" if flask_version >= (2, 2, 0) else "" self.assertEqual(spans[0].resource, resource) # FIXME: should always be ''? diff --git a/tests/contrib/flask/test_views.py b/tests/contrib/flask/test_views.py index eee95b75565..eee9858223e 100644 --- a/tests/contrib/flask/test_views.py +++ b/tests/contrib/flask/test_views.py @@ -10,7 +10,7 @@ base_exception_name = "builtins.Exception" -EXPECTED_METADATA = {"component": "flask", "_dd.base_service": ""} +EXPECTED_METADATA = {"component": "flask", "_dd.base_service": "tests.contrib.flask"} class FlaskViewTestCase(BaseFlaskTestCase): diff --git a/tests/contrib/google_generativeai/test_google_generativeai_llmobs.py b/tests/contrib/google_generativeai/test_google_generativeai_llmobs.py index c897383f3fc..0daaf01d098 100644 --- a/tests/contrib/google_generativeai/test_google_generativeai_llmobs.py +++ b/tests/contrib/google_generativeai/test_google_generativeai_llmobs.py @@ -42,7 +42,7 @@ def test_completion(self, genai, ddtrace_global_config, mock_llmobs_writer, mock ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 12, "output_tokens": 30, "total_tokens": 42}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -67,7 +67,7 @@ async def test_completion_async( ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 12, "output_tokens": 30, "total_tokens": 42}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -95,7 +95,7 @@ def test_completion_error(self, genai, ddtrace_global_config, mock_llmobs_writer error_message=span.get_tag("error.message"), error_stack=span.get_tag("error.stack"), metadata={"temperature": 1.0, "max_output_tokens": 35}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) ) @@ -127,7 +127,7 @@ async def test_completion_error_async( error_message=span.get_tag("error.message"), error_stack=span.get_tag("error.stack"), metadata={"temperature": 1.0, "max_output_tokens": 35}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) ) @@ -160,7 +160,7 @@ def test_completion_multiple_messages( ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -193,7 +193,7 @@ async def test_completion_multiple_messages_async( ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -226,7 +226,7 @@ def test_chat_completion(self, genai, ddtrace_global_config, mock_llmobs_writer, ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -261,7 +261,7 @@ async def test_chat_completion_async( ], metadata={"temperature": 1.0, "max_output_tokens": 35}, token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -296,7 +296,7 @@ def test_completion_system_prompt(self, genai, ddtrace_global_config, mock_llmob ], metadata={"temperature": 1.0, "max_output_tokens": 50}, token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -333,7 +333,7 @@ async def test_completion_system_prompt_async( ], metadata={"temperature": 1.0, "max_output_tokens": 50}, token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -361,7 +361,7 @@ def test_completion_stream(self, genai, ddtrace_global_config, mock_llmobs_write ], metadata={"temperature": 1.0, "max_output_tokens": 60}, token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -391,7 +391,7 @@ async def test_completion_stream_async( ], metadata={"temperature": 1.0, "max_output_tokens": 60}, token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -425,7 +425,7 @@ def test_completion_tool_call(self, genai, ddtrace_global_config, mock_llmobs_wr ], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -461,7 +461,7 @@ async def test_completion_tool_call_async( ], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -502,7 +502,7 @@ def test_gemini_completion_tool_stream( ], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -543,7 +543,7 @@ async def test_gemini_completion_tool_stream_async( ], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -569,7 +569,7 @@ def test_gemini_completion_image(self, genai, ddtrace_global_config, mock_llmobs output_messages=[{"content": "57 100 900 911", "role": "model"}], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) @@ -597,6 +597,6 @@ async def test_gemini_completion_image_async( output_messages=[{"content": "57 100 900 911", "role": "model"}], metadata={"temperature": 1.0, "max_output_tokens": 30}, token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) diff --git a/tests/contrib/httplib/test_httplib.py b/tests/contrib/httplib/test_httplib.py index 0ee762df61a..edc888ad3eb 100644 --- a/tests/contrib/httplib/test_httplib.py +++ b/tests/contrib/httplib/test_httplib.py @@ -167,7 +167,7 @@ def test_httplib_request_get_request(self, query_string=""): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) assert span.get_tag("http.method") == "GET" @@ -216,7 +216,7 @@ def test_httplib_request_get_request_https(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) assert span.get_tag("http.method") == "GET" @@ -244,7 +244,7 @@ def test_httplib_request_post_request(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) assert span.get_tag("http.method") == "POST" @@ -272,7 +272,7 @@ def test_httplib_request_get_request_query_string(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) assert span.get_tag("http.method") == "GET" @@ -305,7 +305,7 @@ def test_httplib_request_500_request(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 1) self.assertEqual(span.get_tag("http.method"), "GET") @@ -338,7 +338,7 @@ def test_httplib_request_non_200_request(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) self.assertEqual(span.get_tag("http.method"), "GET") @@ -426,7 +426,7 @@ def test_urllib_request(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) self.assertEqual(span.get_tag("http.method"), "GET") @@ -462,7 +462,7 @@ def test_urllib_request_https(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) self.assertEqual(span.get_tag("http.method"), "GET") @@ -491,7 +491,7 @@ def test_urllib_request_object(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) self.assertEqual(span.get_tag("http.method"), "GET") @@ -519,7 +519,7 @@ def test_urllib_request_opener(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 0) self.assertEqual(span.get_tag("http.method"), "GET") @@ -571,7 +571,7 @@ def test_httplib_bad_url(self): span = spans[0] self.assert_is_not_measured(span) self.assertEqual(span.span_type, "http") - self.assertIsNone(span.service) + self.assertEqual(span.service, "tests.contrib.httplib") self.assertEqual(span.name, self.SPAN_NAME) self.assertEqual(span.error, 1) self.assertEqual(span.get_tag("http.method"), "GET") diff --git a/tests/contrib/httpx/test_httpx.py b/tests/contrib/httpx/test_httpx.py index 8e2de98f592..43841d73f23 100644 --- a/tests/contrib/httpx/test_httpx.py +++ b/tests/contrib/httpx/test_httpx.py @@ -136,7 +136,7 @@ def assert_spans(test_spans, service): resp = httpx.get(url, headers=DEFAULT_HEADERS) assert resp.status_code == 200 - assert_spans(test_spans, service=None) + assert_spans(test_spans, service="tests.contrib.httpx") resp = client.get(url, headers=DEFAULT_HEADERS) assert resp.status_code == 200 diff --git a/tests/contrib/httpx/test_httpx_pre_0_11.py b/tests/contrib/httpx/test_httpx_pre_0_11.py index d5f85f9ce68..ed4a70e047d 100644 --- a/tests/contrib/httpx/test_httpx_pre_0_11.py +++ b/tests/contrib/httpx/test_httpx_pre_0_11.py @@ -120,7 +120,7 @@ def assert_spans(test_spans, service): resp = await httpx.get(url, headers=DEFAULT_HEADERS) assert resp.status_code == 200 - assert_spans(test_spans, service=None) + assert_spans(test_spans, service="tests.contrib.httpx") resp = await client.get(url, headers=DEFAULT_HEADERS) assert resp.status_code == 200 diff --git a/tests/contrib/jinja2/test_jinja2.py b/tests/contrib/jinja2/test_jinja2.py index e5a53c3481a..fe675ffd9b3 100644 --- a/tests/contrib/jinja2/test_jinja2.py +++ b/tests/contrib/jinja2/test_jinja2.py @@ -42,7 +42,7 @@ def test_render_inline_template(self): assert len(spans) == 2 for span in spans: - assert span.service is None + assert span.service == "tests.contrib.jinja2" assert span.span_type == "template" assert span.get_tag("jinja2.template_name") == "" assert span.get_tag("component") == "jinja2" @@ -61,7 +61,7 @@ def test_generate_inline_template(self): assert len(spans) == 2 for span in spans: - assert span.service is None + assert span.service == "tests.contrib.jinja2" assert span.span_type == "template" assert span.get_tag("jinja2.template_name") == "" assert span.get_tag("component") == "jinja2" @@ -110,7 +110,7 @@ def test_file_template(self): for span in spans: assert span.span_type == "template" - assert span.service is None + assert span.service == "tests.contrib.jinja2" # templates.html extends base.html def get_def(s): diff --git a/tests/contrib/langchain/test_langchain.py b/tests/contrib/langchain/test_langchain.py index 9928723a845..ee7918d920d 100644 --- a/tests/contrib/langchain/test_langchain.py +++ b/tests/contrib/langchain/test_langchain.py @@ -174,7 +174,7 @@ def test_openai_llm_metrics(langchain, request_vcr, mock_metrics, mock_logs, sna expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:text-davinci-003", "langchain.request.type:llm", @@ -213,7 +213,7 @@ def test_llm_logs(langchain, ddtrace_config_langchain, request_vcr, mock_logs, m "message": "sampled langchain.llms.openai.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-davinci-003,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -334,7 +334,7 @@ def test_chat_model_metrics(langchain, request_vcr, mock_metrics, mock_logs, sna expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:gpt-3.5-turbo", "langchain.request.type:chat_model", @@ -373,7 +373,7 @@ def test_chat_model_logs(langchain, ddtrace_config_langchain, request_vcr, mock_ "message": "sampled langchain.chat_models.openai.ChatOpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -424,7 +424,7 @@ def test_openai_embedding_metrics(langchain, request_vcr, mock_metrics, mock_log expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:text-embedding-ada-002", "langchain.request.type:embedding", @@ -457,7 +457,7 @@ def test_embedding_logs(langchain, ddtrace_config_langchain, request_vcr, mock_l "message": "sampled langchain.embeddings.openai.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -635,7 +635,7 @@ def test_openai_chain_metrics(langchain, request_vcr, mock_metrics, mock_logs, s expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:text-davinci-003", mock.ANY, # should be in format "langchain.request.type:" @@ -678,7 +678,7 @@ def test_chain_logs(langchain, ddtrace_config_langchain, request_vcr, mock_logs, "message": "sampled langchain.llms.openai.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-davinci-003,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(llm_span.trace_id)[2:], @@ -693,7 +693,7 @@ def test_chain_logs(langchain, ddtrace_config_langchain, request_vcr, mock_logs, "message": "sampled langchain.chains.llm.LLMChain", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 "dd.trace_id": hex(mid_chain_span.trace_id)[2:], @@ -713,7 +713,7 @@ def test_chain_logs(langchain, ddtrace_config_langchain, request_vcr, mock_logs, "message": "sampled langchain.chains.llm_math.base.LLMMathChain", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 "dd.trace_id": hex(base_chain_span.trace_id)[2:], @@ -849,7 +849,7 @@ def test_vectorstore_similarity_search_metrics(langchain, request_vcr, mock_metr expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:pinecone", "langchain.request.model:", "langchain.request.type:similarity_search", @@ -893,7 +893,7 @@ def test_vectorstore_logs(langchain, ddtrace_config_langchain, request_vcr, mock "message": "sampled langchain.embeddings.openai.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(embeddings_span.trace_id)[2:], @@ -907,7 +907,7 @@ def test_vectorstore_logs(langchain, ddtrace_config_langchain, request_vcr, mock "message": "sampled langchain.vectorstores.pinecone.Pinecone", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:pinecone,langchain.request.model:,langchain.request.type:similarity_search,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(vectorstore_span.trace_id)[2:], @@ -1020,7 +1020,7 @@ def test_llm_logs_when_response_not_completed( "message": "sampled langchain.llms.openai.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-davinci-003,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -1054,7 +1054,7 @@ def test_chat_model_logs_when_response_not_completed( "message": "sampled langchain.chat_models.openai.ChatOpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -1091,7 +1091,7 @@ def test_embedding_logs_when_response_not_completed( "message": "sampled langchain.embeddings.openai.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -1134,7 +1134,7 @@ def test_vectorstore_logs_error(langchain, ddtrace_config_langchain, mock_logs, "message": "sampled langchain.vectorstores.pinecone.Pinecone", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:pinecone,langchain.request.model:,langchain.request.type:similarity_search,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(vectorstore_span.trace_id)[2:], diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py index 6a95b08027d..8fa415ec635 100644 --- a/tests/contrib/langchain/test_langchain_community.py +++ b/tests/contrib/langchain/test_langchain_community.py @@ -157,7 +157,7 @@ def test_openai_llm_metrics( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:gpt-3.5-turbo-instruct", "langchain.request.type:llm", @@ -198,7 +198,7 @@ def test_llm_logs(langchain_openai, ddtrace_config_langchain, request_vcr, mock_ "message": "sampled langchain_openai.llms.base.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -309,7 +309,7 @@ def test_chat_model_metrics( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:gpt-3.5-turbo", "langchain.request.type:chat_model", @@ -352,7 +352,7 @@ def test_chat_model_logs( "message": "sampled langchain_openai.chat_models.base.ChatOpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -414,7 +414,7 @@ def test_openai_embedding_metrics(langchain_openai, request_vcr, mock_metrics, m expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:openai", "langchain.request.model:text-embedding-ada-002", "langchain.request.type:embedding", @@ -449,7 +449,7 @@ def test_embedding_logs(langchain_openai, ddtrace_config_langchain, request_vcr, "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -671,7 +671,7 @@ def test_chain_logs( "message": "sampled langchain_openai.llms.base.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(llm_span.trace_id)[2:], @@ -686,7 +686,7 @@ def test_chain_logs( "message": "sampled langchain.chains.llm.LLMChain", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 "dd.trace_id": hex(mid_chain_span.trace_id)[2:], @@ -706,7 +706,7 @@ def test_chain_logs( "message": "sampled langchain.chains.llm_math.base.LLMMathChain", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 "dd.trace_id": hex(base_chain_span.trace_id)[2:], @@ -823,7 +823,7 @@ def test_vectorstore_similarity_search_metrics(langchain_openai, request_vcr, mo expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.langchain", "langchain.request.provider:pineconevectorstore", "langchain.request.model:", "langchain.request.type:similarity_search", @@ -867,7 +867,7 @@ def test_vectorstore_logs( "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(embeddings_span.trace_id)[2:], @@ -881,7 +881,7 @@ def test_vectorstore_logs( "message": "sampled langchain_pinecone.vectorstores.PineconeVectorStore", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "info", "ddtags": "env:,version:,langchain.request.provider:pineconevectorstore,langchain.request.model:,langchain.request.type:similarity_search,langchain.request.api_key:", # noqa: E501 "dd.trace_id": hex(vectorstore_span.trace_id)[2:], @@ -989,7 +989,7 @@ def test_llm_logs_when_response_not_completed( "message": "sampled langchain_openai.llms.base.OpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -1025,7 +1025,7 @@ def test_chat_model_logs_when_response_not_completed( "message": "sampled langchain_openai.chat_models.base.ChatOpenAI", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], @@ -1070,7 +1070,7 @@ def test_embedding_logs_when_response_not_completed( "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", "hostname": mock.ANY, "ddsource": "langchain", - "service": "", + "service": "tests.contrib.langchain", "status": "error", "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 "dd.trace_id": hex(trace_id)[2:], diff --git a/tests/contrib/langchain/test_langchain_llmobs.py b/tests/contrib/langchain/test_langchain_llmobs.py index feba6f73aa5..ca8fe35883a 100644 --- a/tests/contrib/langchain/test_langchain_llmobs.py +++ b/tests/contrib/langchain/test_langchain_llmobs.py @@ -74,7 +74,7 @@ def _assert_expected_llmobs_llm_span( output_messages=output_messages if not mock_io else mock.ANY, metadata=metadata, token_metrics=metrics, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -85,7 +85,7 @@ def _assert_expected_llmobs_chain_span(span, mock_llmobs_span_writer, input_valu "workflow", input_value=input_value if input_value is not None else mock.ANY, output_value=output_value if output_value is not None else mock.ANY, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) mock_llmobs_span_writer.enqueue.assert_any_call(expected_chain_span_event) @@ -420,7 +420,7 @@ def test_llmobs_embedding_query(self, langchain, mock_llmobs_span_writer, mock_t model_provider="openai", input_documents=[{"text": "hello world"}], output_value="[1 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -445,7 +445,7 @@ def test_llmobs_embedding_documents(self, langchain, mock_llmobs_span_writer, mo model_provider="openai", input_documents=[{"text": "hello world"}, {"text": "goodbye world"}], output_value="[2 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -472,7 +472,7 @@ def test_llmobs_similarity_search(self, langchain, mock_llmobs_span_writer, mock input_value="Who was Alan Turing?", output_documents=[{"text": mock.ANY, "id": mock.ANY, "name": mock.ANY}], output_value="[1 document(s) retrieved]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) mock_llmobs_span_writer.enqueue.assert_any_call(expected_span) assert mock_llmobs_span_writer.enqueue.call_count == 2 @@ -725,7 +725,7 @@ def test_llmobs_embedding_query(self, langchain_community, langchain_openai, moc model_provider="openai", input_documents=[{"text": "hello world"}], output_value="[1 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -751,7 +751,7 @@ def test_llmobs_embedding_documents( model_provider="fake", input_documents=[{"text": "hello world"}, {"text": "goodbye world"}], output_value="[2 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -781,7 +781,7 @@ def test_llmobs_similarity_search(self, langchain_openai, langchain_pinecone, mo {"text": mock.ANY, "id": mock.ANY, "name": "The Evolution of Communication Technologies"} ], output_value="[1 document(s) retrieved]", - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) mock_llmobs_span_writer.enqueue.assert_any_call(expected_span) @@ -828,7 +828,7 @@ def add(a: int, b: int) -> int: ], metadata={"temperature": 0.7}, token_metrics={"input_tokens": mock.ANY, "output_tokens": mock.ANY, "total_tokens": mock.ANY}, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -871,7 +871,7 @@ def circumference_tool(radius: float) -> float: "description": mock.ANY, }, }, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -920,7 +920,7 @@ def test_llmobs_streamed_chain( output_messages=[{"content": "Python is\n\nthe best!", "role": "assistant"}], metadata={"temperature": 0.7}, token_metrics={}, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) @@ -956,7 +956,7 @@ def test_llmobs_streamed_llm( output_messages=[{"content": "\n\nPython is cool!"}], metadata={"temperature": 0.7, "max_tokens": 256}, token_metrics={}, - tags={"ml_app": "langchain_test"}, + tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, ) ) diff --git a/tests/contrib/logging/test_logging.py b/tests/contrib/logging/test_logging.py index 627d333110e..2e85d73e386 100644 --- a/tests/contrib/logging/test_logging.py +++ b/tests/contrib/logging/test_logging.py @@ -99,7 +99,7 @@ def test_patch(self): else: assert not isinstance(logging.StrFormatStyle.format, wrapt.BoundFunctionWrapper) - def _test_logging(self, create_span, service="", version="", env=""): + def _test_logging(self, create_span, service="tests.contrib.logging", version="", env=""): def func(): span = create_span() logger.info("Hello!") @@ -139,10 +139,10 @@ def test_log_trace(self): def create_span(): return self.tracer.trace("test.logging") - self._test_logging(create_span=create_span) + self._test_logging(create_span=create_span, service="") with self.override_global_config(dict(version="global.version", env="global.env")): - self._test_logging(create_span=create_span, version="global.version", env="global.env") + self._test_logging(create_span=create_span, version="global.version", env="global.env", service="") @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED="True")) def test_log_trace_128bit_trace_ids(self): @@ -157,7 +157,7 @@ def create_span(): return span with self.override_global_config(dict(version="v1.666", env="test")): - self._test_logging(create_span=create_span, version="v1.666", env="test") + self._test_logging(create_span=create_span, version="v1.666", env="test", service="") # makes sense that this fails because _test_logging looks for the 64 bit trace id def test_log_trace_service(self): @@ -237,9 +237,8 @@ def func(): lines = output.splitlines() assert ( - "Hello! [dd.service= dd.env= dd.version= dd.trace_id={:032x} dd.span_id={}]".format( - span.trace_id, span.span_id - ) + "Hello! [dd.service=tests.contrib.logging dd.env= dd.version= " + + "dd.trace_id={:032x} dd.span_id={}]".format(span.trace_id, span.span_id) == lines[0] ) @@ -267,10 +266,9 @@ def test_log_strformat_style_format(self): record = logger.makeRecord("name", "INFO", "func", 534, "Manual log record", (), None) log = formatter.format(record) expected = ( - "Manual log record [dd.service= dd.env= dd.version= dd.trace_id={:032x} dd.span_id={}]".format( - span.trace_id, span.span_id - ) - ) + "Manual log record [dd.service=tests.contrib.logging dd.env= dd.version= " + + "dd.trace_id={:032x} dd.span_id={}]" + ).format(span.trace_id, span.span_id) assert log == expected assert not hasattr(record, "dd") diff --git a/tests/contrib/openai/test_openai_llmobs.py b/tests/contrib/openai/test_openai_llmobs.py index d68cc67b2e3..ddba259e928 100644 --- a/tests/contrib/openai/test_openai_llmobs.py +++ b/tests/contrib/openai/test_openai_llmobs.py @@ -37,7 +37,7 @@ def test_completion(self, openai, ddtrace_global_config, mock_llmobs_writer, moc output_messages=[{"content": ", relax!” I said to my laptop"}, {"content": " (1"}], metadata={"temperature": 0.8, "max_tokens": 10, "n": 2, "stop": ".", "user": "ddtrace-test"}, token_metrics={"input_tokens": 2, "output_tokens": 12, "total_tokens": 14}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -59,7 +59,7 @@ def test_completion_stream(self, openai, ddtrace_global_config, mock_llmobs_writ output_messages=[{"content": expected_completion}], metadata={"stream": True}, token_metrics={"input_tokens": 2, "output_tokens": 16, "total_tokens": 18}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ), ) @@ -92,7 +92,7 @@ def test_chat_completion(self, openai, ddtrace_global_config, mock_llmobs_writer output_messages=[{"role": "assistant", "content": choice.message.content} for choice in resp.choices], metadata={"top_p": 0.9, "n": 2, "user": "ddtrace-test"}, token_metrics={"input_tokens": 57, "output_tokens": 34, "total_tokens": 91}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -126,7 +126,7 @@ async def test_chat_completion_stream(self, openai, ddtrace_global_config, mock_ output_messages=[{"content": expected_completion, "role": "assistant"}], metadata={"stream": True, "user": "ddtrace-test"}, token_metrics={"input_tokens": 8, "output_tokens": 12, "total_tokens": 20}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -154,7 +154,7 @@ def test_chat_completion_function_call(self, openai, ddtrace_global_config, mock output_messages=[function_call_expected_output], metadata={"function_call": "auto", "user": "ddtrace-test"}, token_metrics={"input_tokens": 157, "output_tokens": 57, "total_tokens": 214}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -189,7 +189,7 @@ def test_chat_completion_function_call_stream(self, openai, ddtrace_global_confi output_messages=[tool_call_expected_output], metadata={"stream": True, "user": "ddtrace-test", "function_call": "auto"}, token_metrics={"input_tokens": 63, "output_tokens": 33, "total_tokens": 96}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -233,7 +233,7 @@ def test_chat_completion_tool_call(self, openai, ddtrace_global_config, mock_llm output_messages=[expected_output], metadata={"tool_choice": "auto", "user": "ddtrace-test"}, token_metrics={"input_tokens": 157, "output_tokens": 57, "total_tokens": 214}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -265,7 +265,7 @@ def test_completion_error(self, openai, ddtrace_global_config, mock_llmobs_write error="openai.error.AuthenticationError", error_message="Incorrect API key provided: . You can find your API key at https://platform.openai.com/account/api-keys.", # noqa: E501 error_stack=span.get_tag("error.stack"), - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -297,7 +297,7 @@ def test_chat_completion_error(self, openai, ddtrace_global_config, mock_llmobs_ error="openai.error.AuthenticationError", error_message="Incorrect API key provided: . You can find your API key at https://platform.openai.com/account/api-keys.", # noqa: E501 error_stack=span.get_tag("error.stack"), - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -335,7 +335,7 @@ def test_completion(self, openai, ddtrace_global_config, mock_llmobs_writer, moc output_messages=[{"content": ", relax!” I said to my laptop"}, {"content": " (1"}], metadata={"temperature": 0.8, "max_tokens": 10, "n": 2, "stop": ".", "user": "ddtrace-test"}, token_metrics={"input_tokens": 2, "output_tokens": 12, "total_tokens": 14}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -365,7 +365,7 @@ def test_completion_azure( output_messages=[{"content": expected_output}], metadata={"temperature": 0, "max_tokens": 20, "n": 1, "user": "ddtrace-test"}, token_metrics={"input_tokens": 16, "output_tokens": 20, "total_tokens": 36}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -395,7 +395,7 @@ async def test_completion_azure_async( output_messages=[{"content": expected_output}], metadata={"temperature": 0, "max_tokens": 20, "n": 1, "user": "ddtrace-test"}, token_metrics={"input_tokens": 16, "output_tokens": 20, "total_tokens": 36}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -422,7 +422,7 @@ def test_completion_stream(self, openai, ddtrace_global_config, mock_llmobs_writ output_messages=[{"content": expected_completion}], metadata={"stream": True}, token_metrics={"input_tokens": 2, "output_tokens": 2, "total_tokens": 4}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ), ) @@ -454,7 +454,7 @@ def test_chat_completion(self, openai, ddtrace_global_config, mock_llmobs_writer output_messages=[{"role": "assistant", "content": choice.message.content} for choice in resp.choices], metadata={"top_p": 0.9, "n": 2, "user": "ddtrace-test"}, token_metrics={"input_tokens": 57, "output_tokens": 34, "total_tokens": 91}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -484,7 +484,7 @@ def test_chat_completion_azure( output_messages=[{"role": "assistant", "content": expected_output}], metadata={"temperature": 0, "max_tokens": 20, "n": 1, "user": "ddtrace-test"}, token_metrics={"input_tokens": 18, "output_tokens": 20, "total_tokens": 38}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -514,7 +514,7 @@ async def test_chat_completion_azure_async( output_messages=[{"role": "assistant", "content": expected_output}], metadata={"temperature": 0, "max_tokens": 20, "n": 1, "user": "ddtrace-test"}, token_metrics={"input_tokens": 18, "output_tokens": 20, "total_tokens": 38}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -549,7 +549,7 @@ def test_chat_completion_stream(self, openai, ddtrace_global_config, mock_llmobs output_messages=[{"content": expected_completion, "role": "assistant"}], metadata={"stream": True, "user": "ddtrace-test"}, token_metrics={"input_tokens": 8, "output_tokens": 8, "total_tokens": 16}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -584,7 +584,7 @@ def test_chat_completion_stream_tokens(self, openai, ddtrace_global_config, mock output_messages=[{"content": expected_completion, "role": "assistant"}], metadata={"stream": True, "stream_options": {"include_usage": True}}, token_metrics={"input_tokens": 17, "output_tokens": 19, "total_tokens": 36}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -627,7 +627,7 @@ def test_chat_completion_function_call(self, openai, ddtrace_global_config, mock output_messages=[expected_output], metadata={"function_call": "auto", "user": "ddtrace-test"}, token_metrics={"input_tokens": 157, "output_tokens": 57, "total_tokens": 214}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -653,7 +653,7 @@ def test_chat_completion_tool_call(self, openai, ddtrace_global_config, mock_llm output_messages=[tool_call_expected_output], metadata={"user": "ddtrace-test"}, token_metrics={"input_tokens": 157, "output_tokens": 57, "total_tokens": 214}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -686,7 +686,7 @@ def test_chat_completion_tool_call_stream(self, openai, ddtrace_global_config, m output_messages=[tool_call_expected_output], metadata={"user": "ddtrace-test", "stream": True, "stream_options": {"include_usage": True}}, token_metrics={"input_tokens": 166, "output_tokens": 43, "total_tokens": 209}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -719,7 +719,7 @@ def test_completion_error(self, openai, ddtrace_global_config, mock_llmobs_write error="openai.AuthenticationError", error_message="Error code: 401 - {'error': {'message': 'Incorrect API key provided: . You can find your API key at https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_api_key'}}", # noqa: E501 error_stack=span.get_tag("error.stack"), - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -752,7 +752,7 @@ def test_chat_completion_error(self, openai, ddtrace_global_config, mock_llmobs_ error="openai.AuthenticationError", error_message="Error code: 401 - {'error': {'message': 'Incorrect API key provided: . You can find your API key at https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_api_key'}}", # noqa: E501 error_stack=span.get_tag("error.stack"), - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -772,7 +772,7 @@ def test_embedding_string(self, openai, ddtrace_global_config, mock_llmobs_write input_documents=[{"text": "hello world"}], output_value="[1 embedding(s) returned with size 1536]", token_metrics={"input_tokens": 2, "output_tokens": 0, "total_tokens": 2}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -792,7 +792,7 @@ def test_embedding_string_array(self, openai, ddtrace_global_config, mock_llmobs input_documents=[{"text": "hello world"}, {"text": "hello again"}], output_value="[2 embedding(s) returned with size 1536]", token_metrics={"input_tokens": 4, "output_tokens": 0, "total_tokens": 4}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -812,7 +812,7 @@ def test_embedding_token_array(self, openai, ddtrace_global_config, mock_llmobs_ input_documents=[{"text": "[1111, 2222, 3333]"}], output_value="[1 embedding(s) returned with size 1536]", token_metrics={"input_tokens": 3, "output_tokens": 0, "total_tokens": 3}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -838,7 +838,7 @@ def test_embedding_array_of_token_arrays(self, openai, ddtrace_global_config, mo ], output_value="[3 embedding(s) returned with size 1536]", token_metrics={"input_tokens": 9, "output_tokens": 0, "total_tokens": 9}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -863,7 +863,7 @@ def test_embedding_string_base64(self, openai, ddtrace_global_config, mock_llmob input_documents=[{"text": "hello world"}], output_value="[1 embedding(s) returned]", token_metrics={"input_tokens": 2, "output_tokens": 0, "total_tokens": 2}, - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) ) @@ -893,7 +893,7 @@ def test_unserializable_param_is_handled(self, openai, ddtrace_global_config, mo error=span.get_tag("error.type"), error_message=span.get_tag("error.message"), error_stack=span.get_tag("error.stack"), - tags={"ml_app": ""}, + tags={"ml_app": "", "service": "tests.contrib.openai"}, ) mock_llmobs_writer.enqueue.assert_called_with(expected_span) actual_span = mock_llmobs_writer.enqueue.call_args[0][0] diff --git a/tests/contrib/openai/test_openai_v0.py b/tests/contrib/openai/test_openai_v0.py index 1bbb8400179..04654f4a4cf 100644 --- a/tests/contrib/openai/test_openai_v0.py +++ b/tests/contrib/openai/test_openai_v0.py @@ -149,7 +149,7 @@ def test_completion( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -219,7 +219,7 @@ async def test_acompletion( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:curie", "model:curie", "openai.request.endpoint:/v1/completions", @@ -278,7 +278,7 @@ def test_logs_completions(openai_vcr, openai, ddtrace_config_openai, mock_logs, "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/completions,openai.request.method:POST,openai.request.model:ada,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -540,7 +540,7 @@ def test_logs_image_create(openai_vcr, openai, ddtrace_config_openai, mock_logs, "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/images/generations,openai.request.method:POST,openai.request.model:dall-e,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -627,7 +627,7 @@ def test_logs_image_edit(openai_vcr, openai, ddtrace_config_openai, mock_logs, m "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/images/edits,openai.request.method:POST,openai.request.model:dall-e,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -711,7 +711,7 @@ def test_logs_image_variation(openai_vcr, openai, ddtrace_config_openai, mock_lo "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/images/variations,openai.request.method:POST,openai.request.model:dall-e,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -1286,7 +1286,7 @@ def test_completion_stream(openai, openai_vcr, mock_metrics, mock_tracer): expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -1327,7 +1327,7 @@ async def test_completion_async_stream(openai, openai_vcr, mock_metrics, mock_tr expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -1375,7 +1375,7 @@ def test_chat_completion_stream(openai, openai_vcr, mock_metrics, snapshot_trace expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", @@ -1426,7 +1426,7 @@ async def test_chat_completion_async_stream(openai, openai_vcr, mock_metrics, sn expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index c6598e4d9e5..f13de144fc5 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -166,7 +166,7 @@ def test_completion( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -236,7 +236,7 @@ async def test_acompletion( expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:curie", "model:curie", "openai.request.endpoint:/v1/completions", @@ -300,7 +300,7 @@ def test_logs_completions(openai_vcr, openai, ddtrace_config_openai, mock_logs, "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/completions,openai.request.method:POST,openai.request.model:ada,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -589,7 +589,7 @@ def test_logs_image_create(openai_vcr, openai, ddtrace_config_openai, mock_logs, "message": mock.ANY, "hostname": mock.ANY, "ddsource": "openai", - "service": "", + "service": "tests.contrib.openai", "status": "info", "ddtags": "env:,version:,openai.request.endpoint:/v1/images/generations,openai.request.method:POST,openai.request.model:dall-e,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 "dd.trace_id": "{:x}".format(trace_id), @@ -942,7 +942,7 @@ def test_completion_stream(openai, openai_vcr, mock_metrics, mock_tracer): expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -981,7 +981,7 @@ async def test_completion_async_stream(openai, openai_vcr, mock_metrics, mock_tr expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -1024,7 +1024,7 @@ def test_completion_stream_context_manager(openai, openai_vcr, mock_metrics, moc expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:ada", "model:ada", "openai.request.endpoint:/v1/completions", @@ -1071,7 +1071,7 @@ def test_chat_completion_stream(openai, openai_vcr, mock_metrics, snapshot_trace expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", @@ -1120,7 +1120,7 @@ async def test_chat_completion_async_stream(openai, openai_vcr, mock_metrics, sn expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", @@ -1170,7 +1170,7 @@ def test_chat_completion_stream_tokens(openai, openai_vcr, mock_metrics, snapsho expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", @@ -1223,7 +1223,7 @@ async def test_chat_completion_async_stream_context_manager(openai, openai_vcr, expected_tags = [ "version:", "env:", - "service:", + "service:tests.contrib.openai", "openai.request.model:gpt-3.5-turbo", "model:gpt-3.5-turbo", "openai.request.endpoint:/v1/chat/completions", diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index 49ca4966d65..2d1c04267ed 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -3,7 +3,6 @@ import pytest from ddtrace.internal.agent import get_trace_url -from ddtrace.internal.utils.config import get_application_name from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.version import get_version @@ -49,7 +48,7 @@ def test_snapshot_intake_url(): def test_service_name(): - assert DynamicInstrumentationConfig().service_name == get_application_name() + assert DynamicInstrumentationConfig().service_name == "tests.debugging" with debugger_config(DD_SERVICE="test-service") as config: assert config.service_name == "test-service" diff --git a/tests/integration/test_debug.py b/tests/integration/test_debug.py index 18018c3e854..e699db36d6b 100644 --- a/tests/integration/test_debug.py +++ b/tests/integration/test_debug.py @@ -94,7 +94,7 @@ def test_standard_tags(): assert f.get("tracer_enabled") is True assert f.get("sampler_type") == "DatadogSampler" assert f.get("priority_sampler_type") == "N/A" - assert f.get("service") == "" + assert f.get("service") == "tests.integration" assert f.get("dd_version") == "" assert f.get("debug") is False assert f.get("enabled_cli") is False diff --git a/tests/internal/remoteconfig/test_remoteconfig_client_e2e.py b/tests/internal/remoteconfig/test_remoteconfig_client_e2e.py index 39e84c288a4..20bb2d8fba0 100644 --- a/tests/internal/remoteconfig/test_remoteconfig_client_e2e.py +++ b/tests/internal/remoteconfig/test_remoteconfig_client_e2e.py @@ -41,7 +41,7 @@ def _expected_payload( "runtime_id": runtime.get_runtime_id(), "language": "python", "tracer_version": _pep440_to_semver(), - "service": None, + "service": "tests.internal", "extra_services": [], "env": None, "app_version": None, diff --git a/tests/internal/service_name/test_inferred_base_service.py b/tests/internal/service_name/test_inferred_base_service.py index fa79d3ad785..1e9d40636e6 100644 --- a/tests/internal/service_name/test_inferred_base_service.py +++ b/tests/internal/service_name/test_inferred_base_service.py @@ -20,6 +20,9 @@ def mock_file_system(): (base_path / "venv" / "bin" / "python3.11").mkdir(parents=True) (base_path / "venv" / "bin" / "gunicorn").mkdir(parents=True) + # add a test dir + (base_path / "tests" / "contrib" / "aiohttp").mkdir(parents=True) + (base_path / "modules" / "m1" / "first" / "nice" / "package").mkdir(parents=True) (base_path / "modules" / "m2").mkdir(parents=True) (base_path / "modules" / "no" / "python_files").mkdir(parents=True) @@ -41,6 +44,11 @@ def mock_file_system(): (base_path / "apps" / "app2" / "cmd" / "run.py").touch() (base_path / "apps" / "app2" / "setup.py").touch() + (base_path / "tests" / "contrib" / "aiohttp" / "test.py").touch() + (base_path / "tests" / "contrib" / "aiohttp" / "__init__.py").touch() + (base_path / "tests" / "contrib" / "__init__.py").touch() + (base_path / "tests" / "__init__.py").touch() + # Additional edge cases (base_path / "modules" / "no" / "python_files" / "here.txt").touch() # Module with no subdirectories (base_path / "modules" / "m1" / "first" / "nice" / "package" / "not_a_python_file.txt").touch() @@ -69,6 +77,9 @@ def mock_file_system(): ("python3.12 apps/app2/cmd/run.py", "app2"), ("python -m m1.first.nice.package", "m1.first.nice.package"), ("python -m http.server 8000", "http.server"), + # pytest + ("pytest tests/contrib/aiohttp", "tests.contrib.aiohttp"), + ("pytest --ddtrace tests/contrib/aiohttp", "tests.contrib.aiohttp"), ], ) def test_python_detector(cmd, expected, mock_file_system): diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index fb48d15431b..d39e69808fb 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -34,7 +34,7 @@ def _expected_llmobs_tags(span, error=None, tags=None, session_id=None): expected_tags = [ "version:{}".format(tags.get("version", "")), "env:{}".format(tags.get("env", "")), - "service:{}".format(tags.get("service", "")), + "service:{}".format(tags.get("service", "tests.llmobs")), "source:integration", "ml_app:{}".format(tags.get("ml_app", "unnamed-ml-app")), "ddtrace.version:{}".format(ddtrace.__version__), @@ -178,13 +178,7 @@ def _expected_llmobs_non_llm_span_event( def _llmobs_base_span_event( - span, - span_kind, - tags=None, - session_id=None, - error=None, - error_message=None, - error_stack=None, + span, span_kind, tags=None, session_id=None, error=None, error_message=None, error_stack=None ): span_event = { "trace_id": "{:x}".format(span.trace_id), @@ -268,7 +262,7 @@ def _completion_event(): "parent_id": "", "session_id": "98765432101", "name": "completion_span", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223236, "duration": 12345678900, "error": 0, @@ -299,7 +293,7 @@ def _chat_completion_event(): "parent_id": "", "session_id": "98765432102", "name": "chat_completion_span", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223936, "duration": 12345678900, "error": 0, @@ -337,7 +331,7 @@ def _large_event(): "parent_id": "", "session_id": "98765432103", "name": "large_span", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223936, "duration": 12345678900, "error": 0, @@ -375,7 +369,7 @@ def _oversized_llm_event(): "parent_id": "", "session_id": "98765432104", "name": "oversized_llm_event", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223936, "duration": 12345678900, "error": 0, @@ -413,7 +407,7 @@ def _oversized_workflow_event(): "parent_id": "", "session_id": "98765432105", "name": "oversized_workflow_event", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223936, "duration": 12345678900, "error": 0, @@ -433,7 +427,7 @@ def _oversized_retrieval_event(): "parent_id": "", "session_id": "98765432106", "name": "oversized_retrieval_event", - "tags": ["version:", "env:", "service:", "source:integration"], + "tags": ["version:", "env:", "service:tests.llmobs", "source:integration"], "start_ns": 1707763310981223936, "duration": 12345678900, "error": 0, @@ -450,7 +444,7 @@ def expected_ragas_trace_tags(): return [ "version:", "env:", - "service:", + "service:tests.llmobs", "source:integration", "ml_app:dd-ragas-unnamed-ml-app", "ddtrace.version:{}".format(ddtrace.__version__), diff --git a/tests/llmobs/test_llmobs_span_agent_writer.py b/tests/llmobs/test_llmobs_span_agent_writer.py index 010faf4f2c0..76fe0f21aef 100644 --- a/tests/llmobs/test_llmobs_span_agent_writer.py +++ b/tests/llmobs/test_llmobs_span_agent_writer.py @@ -55,9 +55,9 @@ def test_truncating_oversized_events(mock_writer_logs, mock_http_writer_send_pay llmobs_span_writer.enqueue(_oversized_workflow_event()) mock_writer_logs.warning.assert_has_calls( [ - mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400708), - mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400448), - mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400429), + mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400720), + mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400460), + mock.call("dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400441), ] ) diff --git a/tests/llmobs/test_llmobs_span_agentless_writer.py b/tests/llmobs/test_llmobs_span_agentless_writer.py index 7d479023726..96dc26e5146 100644 --- a/tests/llmobs/test_llmobs_span_agentless_writer.py +++ b/tests/llmobs/test_llmobs_span_agentless_writer.py @@ -63,13 +63,13 @@ def test_truncating_oversized_events(mock_writer_logs, mock_http_writer_send_pay mock_writer_logs.warning.assert_has_calls( [ mock.call( - "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400708 + "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400720 ), mock.call( - "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400448 + "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400460 ), mock.call( - "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400429 + "dropping event input/output because its size (%d) exceeds the event size limit (1MB)", 1400441 ), ] ) diff --git a/tests/llmobs/test_llmobs_trace_processor.py b/tests/llmobs/test_llmobs_trace_processor.py index bfbb17cdd52..e816cd1742a 100644 --- a/tests/llmobs/test_llmobs_trace_processor.py +++ b/tests/llmobs/test_llmobs_trace_processor.py @@ -71,7 +71,9 @@ def test_processor_creates_llmobs_span_event(): trace = [root_llm_span] trace_filter.process_trace(trace) assert mock_llmobs_span_writer.enqueue.call_count == 1 - mock_llmobs_span_writer.assert_has_calls([mock.call.enqueue(_expected_llmobs_llm_span_event(root_llm_span, "llm"))]) + mock_llmobs_span_writer.assert_has_calls( + [mock.call.enqueue(_expected_llmobs_llm_span_event(root_llm_span, "llm", tags={"service": ""}))] + ) def test_processor_only_creates_llmobs_span_event(): diff --git a/tests/snapshots/test_extended_sampling_float_special_case_do_not_match.json b/tests/snapshots/test_extended_sampling_float_special_case_do_not_match.json index 8fe12f17645..3cd67abe1cc 100644 --- a/tests/snapshots/test_extended_sampling_float_special_case_do_not_match.json +++ b/tests/snapshots/test_extended_sampling_float_special_case_do_not_match.json @@ -1,7 +1,7 @@ [[ { "name": "should_send", - "service": "", + "service": "tests.integration", "resource": "should_send", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_float_special_case_match_star.json b/tests/snapshots/test_extended_sampling_float_special_case_match_star.json index 3e39035636d..e619d6b013f 100644 --- a/tests/snapshots/test_extended_sampling_float_special_case_match_star.json +++ b/tests/snapshots/test_extended_sampling_float_special_case_match_star.json @@ -1,7 +1,7 @@ [[ { "name": "should_send", - "service": "", + "service": "tests.integration", "resource": "should_send", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_glob_multi_rule.json b/tests/snapshots/test_extended_sampling_glob_multi_rule.json index 0535ab93778..36dbf58ae54 100644 --- a/tests/snapshots/test_extended_sampling_glob_multi_rule.json +++ b/tests/snapshots/test_extended_sampling_glob_multi_rule.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-3", "_dd.p.tid": "65cd344600000000", "language": "python", diff --git a/tests/snapshots/test_extended_sampling_resource.json b/tests/snapshots/test_extended_sampling_resource.json index f3f8f9b5014..d0f1034dda2 100644 --- a/tests/snapshots/test_extended_sampling_resource.json +++ b/tests/snapshots/test_extended_sampling_resource.json @@ -1,7 +1,7 @@ [[ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ [ { "name": "should_send", - "service": "", + "service": "tests.integration", "resource": "something else", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags.json b/tests/snapshots/test_extended_sampling_tags.json index 4e6ad4e6840..da960c62d23 100644 --- a/tests/snapshots/test_extended_sampling_tags.json +++ b/tests/snapshots/test_extended_sampling_tags.json @@ -1,7 +1,7 @@ [[ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "should_not_send", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_send", - "service": "", + "service": "tests.integration", "resource": "should_send", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags_and_name_glob.json b/tests/snapshots/test_extended_sampling_tags_and_name_glob.json index 290c22b972a..611407e3a38 100644 --- a/tests/snapshots/test_extended_sampling_tags_and_name_glob.json +++ b/tests/snapshots/test_extended_sampling_tags_and_name_glob.json @@ -1,7 +1,7 @@ [[ { "name": "mycoolname", - "service": "", + "service": "tests.integration", "resource": "mycoolname", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "should_send1", "trace_id": 1, "span_id": 1, @@ -53,7 +53,7 @@ [ { "name": "should_send2", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 2, "span_id": 1, @@ -79,7 +79,7 @@ [ { "name": "mycoolname", - "service": "", + "service": "tests.integration", "resource": "mycoolname", "trace_id": 3, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags_and_resource.json b/tests/snapshots/test_extended_sampling_tags_and_resource.json index 55e7e74bce4..252d257da44 100644 --- a/tests/snapshots/test_extended_sampling_tags_and_resource.json +++ b/tests/snapshots/test_extended_sampling_tags_and_resource.json @@ -1,7 +1,7 @@ [[ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "should_send1", "trace_id": 1, "span_id": 1, @@ -52,7 +52,7 @@ [ { "name": "should_send2", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 2, "span_id": 1, @@ -77,7 +77,7 @@ [ { "name": "should_send3", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 3, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags_and_resource_glob.json b/tests/snapshots/test_extended_sampling_tags_and_resource_glob.json index a5d4bfec689..552a68dc586 100644 --- a/tests/snapshots/test_extended_sampling_tags_and_resource_glob.json +++ b/tests/snapshots/test_extended_sampling_tags_and_resource_glob.json @@ -1,7 +1,7 @@ [[ { "name": "should_send3", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "should_send1", "trace_id": 1, "span_id": 1, @@ -53,7 +53,7 @@ [ { "name": "should_send2", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 2, "span_id": 1, @@ -79,7 +79,7 @@ [ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 3, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags_and_service_glob.json b/tests/snapshots/test_extended_sampling_tags_and_service_glob.json index f5441f9f7f5..c8d2ed839f5 100644 --- a/tests/snapshots/test_extended_sampling_tags_and_service_glob.json +++ b/tests/snapshots/test_extended_sampling_tags_and_service_glob.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "65cbae1700000000", "banana": "True", @@ -28,7 +28,7 @@ [ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "should_send1", "trace_id": 1, "span_id": 1, @@ -54,7 +54,7 @@ [ { "name": "should_send2", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 2, "span_id": 1, @@ -88,7 +88,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-3", "_dd.p.tid": "65cbae1700000000", "language": "python", diff --git a/tests/snapshots/test_extended_sampling_tags_glob.json b/tests/snapshots/test_extended_sampling_tags_glob.json index 67c282c5c64..294a062b219 100644 --- a/tests/snapshots/test_extended_sampling_tags_glob.json +++ b/tests/snapshots/test_extended_sampling_tags_glob.json @@ -1,7 +1,7 @@ [[ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "should_not_send", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_send", - "service": "", + "service": "tests.integration", "resource": "should_send", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_tags_glob_insensitive_case_match.json b/tests/snapshots/test_extended_sampling_tags_glob_insensitive_case_match.json index e7218ad60df..fe2d1d2e6af 100644 --- a/tests/snapshots/test_extended_sampling_tags_glob_insensitive_case_match.json +++ b/tests/snapshots/test_extended_sampling_tags_glob_insensitive_case_match.json @@ -1,7 +1,7 @@ [[ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "bananA", "trace_id": 0, "span_id": 1, @@ -28,7 +28,7 @@ [ { "name": "should_send2", - "service": "", + "service": "tests.integration", "resource": "ban", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_w_None.json b/tests/snapshots/test_extended_sampling_w_None.json index 8850a8d8109..edc5670410f 100644 --- a/tests/snapshots/test_extended_sampling_w_None.json +++ b/tests/snapshots/test_extended_sampling_w_None.json @@ -1,7 +1,7 @@ [[ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_not_send2", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 1, "span_id": 1, @@ -54,7 +54,7 @@ [ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 2, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_w_None_meta.json b/tests/snapshots/test_extended_sampling_w_None_meta.json index de6c8a198f7..d4aa1e6b7fe 100644 --- a/tests/snapshots/test_extended_sampling_w_None_meta.json +++ b/tests/snapshots/test_extended_sampling_w_None_meta.json @@ -1,7 +1,7 @@ [[ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_not_send2", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 1, "span_id": 1, @@ -54,7 +54,7 @@ [ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 2, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_w_metrics.json b/tests/snapshots/test_extended_sampling_w_metrics.json index ae6400d044f..9b8d7e07080 100644 --- a/tests/snapshots/test_extended_sampling_w_metrics.json +++ b/tests/snapshots/test_extended_sampling_w_metrics.json @@ -1,7 +1,7 @@ [[ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_not_send2", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 1, "span_id": 1, @@ -54,7 +54,7 @@ [ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 2, "span_id": 1, diff --git a/tests/snapshots/test_extended_sampling_w_tags_none.json b/tests/snapshots/test_extended_sampling_w_tags_none.json index d54834c1a2d..83f7d909f6a 100644 --- a/tests/snapshots/test_extended_sampling_w_tags_none.json +++ b/tests/snapshots/test_extended_sampling_w_tags_none.json @@ -1,7 +1,7 @@ [[ { "name": "should_send1", - "service": "", + "service": "tests.integration", "resource": "banana", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "should_not_send2", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 1, "span_id": 1, @@ -54,7 +54,7 @@ [ { "name": "should_not_send", - "service": "", + "service": "tests.integration", "resource": "mycoolre$ource", "trace_id": 2, "span_id": 1, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_1.json b/tests/snapshots/test_sampling_with_default_sample_rate_1.json index e9ec59dbab2..547a4ca82b2 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_1.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_1.json @@ -1,7 +1,7 @@ [[ { "name": "trace2", - "service": "", + "service": "tests.integration", "resource": "trace2", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_drop.json b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_drop.json index e26c5210293..60276609bef 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_drop.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_drop.json @@ -1,7 +1,7 @@ [[ { "name": "trace6", - "service": "", + "service": "tests.integration", "resource": "trace6", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_keep.json b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_keep.json index 043684a6765..66964f19158 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_keep.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_manual_keep.json @@ -1,7 +1,7 @@ [[ { "name": "trace7", - "service": "", + "service": "tests.integration", "resource": "trace7", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_0.json b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_0.json index 88bbaef249d..dc692652c17 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_0.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_0.json @@ -1,7 +1,7 @@ [[ { "name": "trace5", - "service": "", + "service": "tests.integration", "resource": "trace5", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_1.json b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_1.json index 6674e845323..1236c727cd1 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_1.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_1_and_rule_1.json @@ -1,7 +1,7 @@ [[ { "name": "trace4", - "service": "", + "service": "tests.integration", "resource": "trace4", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_default_sample_rate_tiny.json b/tests/snapshots/test_sampling_with_default_sample_rate_tiny.json index 15b5649d93a..60298f1a984 100644 --- a/tests/snapshots/test_sampling_with_default_sample_rate_tiny.json +++ b/tests/snapshots/test_sampling_with_default_sample_rate_tiny.json @@ -1,7 +1,7 @@ [[ { "name": "trace3", - "service": "", + "service": "tests.integration", "resource": "trace3", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_defaults.json b/tests/snapshots/test_sampling_with_defaults.json index 192453b70ba..ecb2e299e76 100644 --- a/tests/snapshots/test_sampling_with_defaults.json +++ b/tests/snapshots/test_sampling_with_defaults.json @@ -1,7 +1,7 @@ [[ { "name": "trace1", - "service": "", + "service": "tests.integration", "resource": "trace1", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_rate_limit_3.json b/tests/snapshots/test_sampling_with_rate_limit_3.json index 267c8e68033..135ef02a789 100644 --- a/tests/snapshots/test_sampling_with_rate_limit_3.json +++ b/tests/snapshots/test_sampling_with_rate_limit_3.json @@ -1,7 +1,7 @@ [[ { "name": "trace5", - "service": "", + "service": "tests.integration", "resource": "trace5", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_rate_sampler_with_tiny_rate.json b/tests/snapshots/test_sampling_with_rate_sampler_with_tiny_rate.json index 6c9aa52bfa9..41c55f34c1b 100644 --- a/tests/snapshots/test_sampling_with_rate_sampler_with_tiny_rate.json +++ b/tests/snapshots/test_sampling_with_rate_sampler_with_tiny_rate.json @@ -1,7 +1,7 @@ [[ { "name": "trace8", - "service": "", + "service": "tests.integration", "resource": "trace8", "trace_id": 0, "span_id": 1, @@ -23,7 +23,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_0.json b/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_0.json index fc6535969a8..d93076528ee 100644 --- a/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_0.json +++ b/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_0.json @@ -1,7 +1,7 @@ [[ { "name": "trace5", - "service": "", + "service": "tests.integration", "resource": "trace5", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_3_and_rule_0.json b/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_3_and_rule_0.json index d8986904cf1..a445d6f10b5 100644 --- a/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_3_and_rule_0.json +++ b/tests/snapshots/test_sampling_with_sample_rate_1_and_rate_limit_3_and_rule_0.json @@ -1,7 +1,7 @@ [[ { "name": "trace5", - "service": "", + "service": "tests.integration", "resource": "trace5", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index 0fcdb60d1e3..7910618da6f 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -1,7 +1,7 @@ [[ { "name": "test", - "service": null, + "service": "tests.appsec.appsec", "resource": "test", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index 28175b2530a..ca97ab0281e 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -1,7 +1,7 @@ [[ { "name": "test", - "service": null, + "service": "tests.appsec.appsec", "resource": "test", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index 86780e6d037..3137305e022 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -11,7 +11,7 @@ "_dd.appsec.event_rules.version": "1.13.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.waf.version": "1.20.1", - "_dd.base_service": "", + "_dd.base_service": "tests.appsec.appsec", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index 10e49c59b12..def21246eeb 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -11,7 +11,7 @@ "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", "_dd.appsec.waf.version": "1.20.1", - "_dd.base_service": "", + "_dd.base_service": "tests.appsec.appsec", "_dd.p.dm": "-0", "_dd.runtime_family": "python", "http.status_code": "404", diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request.json index dda9dfc805a..1a407e674da 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request_post.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request_post.json index 6eb375b803b..ad72fff13c5 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request_post.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_200_request_post.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_500_request.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_500_request.json index fa783896518..791a4538acc 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_500_request.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_500_request.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_auth_200_request.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_auth_200_request.json index 827258ad1e7..63d69f2ead7 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_auth_200_request.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_auth_200_request.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_split_by_domain.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_split_by_domain.json index 41584231ab8..efead94df1c 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_split_by_domain.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_configure_service_name_split_by_domain.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiohttp", "_dd.p.dm": "-0", "_dd.p.tid": "6583754d00000000", "component": "aiohttp_client", @@ -41,7 +41,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiohttp", "_dd.p.tid": "6583754d00000000", "component": "aiohttp" }, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_multiple.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_multiple.json index e3ab3c9880e..edb367fd9cb 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_multiple.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_multiple.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -32,7 +32,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, @@ -49,7 +49,7 @@ [ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 1, "span_id": 1, @@ -80,7 +80,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 1, "span_id": 2, @@ -97,7 +97,7 @@ [ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 2, "span_id": 1, @@ -128,7 +128,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 2, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_parenting.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_parenting.json index 2712751f617..36887f31e76 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_parenting.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_parenting.json @@ -1,7 +1,7 @@ [[ { "name": "parent", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "parent", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 2, @@ -47,7 +47,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_query_string.json b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_query_string.json index 206ff182af0..4f7e518a8eb 100644 --- a/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_query_string.json +++ b/tests/snapshots/tests.contrib.aiohttp.test_aiohttp_client.test_trace_query_string.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.request", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "aiohttp.request", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ }, { "name": "TCPConnector.connect", - "service": "", + "service": "tests.contrib.aiohttp", "resource": "TCPConnector.connect", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot.json b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot.json index 50db03ecf9f..8af91b31f48 100644 --- a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot.json +++ b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot.json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.template", - "service": "", + "service": "tests.contrib.aiohttp_jinja2", "resource": "aiohttp.template", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot[pyloop].json b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot[pyloop].json index 64319e8422e..9b8a03252ff 100644 --- a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot[pyloop].json +++ b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot[pyloop].json @@ -1,7 +1,7 @@ [[ { "name": "aiohttp.template", - "service": "", + "service": "tests.contrib.aiohttp_jinja2", "resource": "aiohttp.template", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot_patched_server[True].json b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot_patched_server[True].json index 9b3b89eadac..e7091a55a3d 100644 --- a/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot_patched_server[True].json +++ b/tests/snapshots/tests.contrib.aiohttp_jinja2.test_aiohttp_jinja2.test_template_rendering_snapshot_patched_server[True].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiohttp_jinja2", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aiohttp", @@ -42,7 +42,7 @@ "type": "template", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiohttp_jinja2", "_dd.p.tid": "654a694400000000", "aiohttp.template": "/template.jinja2", "component": "aiohttp_jinja2" diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm.json index ecb97a7f031..dd9e9d1fcce 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_create_image.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_create_image.json index f519f68b9a6..8fd97b4e689 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_create_image.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_create_image.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_error.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_error.json index 89b9759808c..4b6cae0a44f 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_error.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_error.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts.json index aebc2405be8..b7a44201a28 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts_with_chat_history.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts_with_chat_history.json index b2cebc0475a..996c8fbe4e3 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts_with_chat_history.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_multiple_prompts_with_chat_history.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream.json index 2a0c370ddcf..640b54e63c0 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_helper.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_helper.json index da73b6cbde3..6ec03a36ca7 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_helper.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_helper.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.stream", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_image.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_image.json index 47fb207abdf..da78521c60d 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_image.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_stream_image.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools.json index 0116cee4321..da752962101 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use.json index abc48a69302..3aeeba536e7 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, @@ -50,7 +50,7 @@ [ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use_stream.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use_stream.json index d5a327d13ba..f351098308e 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use_stream.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_full_use_stream.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "AsyncMessages.stream", "trace_id": 0, "span_id": 1, @@ -43,7 +43,7 @@ [ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "AsyncMessages.create", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream.json index d6fe5ae1a23..f0708e6700e 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "AsyncMessages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream_helper.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream_helper.json index 4cc640e5e36..43c4f9d53da 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream_helper.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_tools_stream_helper.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "AsyncMessages.stream", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_unserializable_arg.json b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_unserializable_arg.json index ae744a6ed0c..db9da915001 100644 --- a/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_unserializable_arg.json +++ b/tests/snapshots/tests.contrib.anthropic.test_anthropic.test_anthropic_llm_unserializable_arg.json @@ -1,7 +1,7 @@ [[ { "name": "anthropic.request", - "service": "", + "service": "tests.contrib.anthropic", "resource": "Messages.create", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_with_rate.json index 16adeb87a19..d870b797213 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_with_rate.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_with_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_without_rate.json index 391ca34cfda..fd592f2416e 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_without_rate.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_analytics_without_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_basics.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_basics.json index 2f71c1d7be0..f1f88c60a6d 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_basics.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_basics.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_cmd_max_length.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_cmd_max_length.json index 82443d557ee..d1172267de5 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_cmd_max_length.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_cmd_max_length.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_config[True].json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_config[True].json index 3bd221c8f74..fe6369a2954 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_config[True].json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_full_command_in_resource_config[True].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "language": "python", "runtime-id": "9ac61da02efc4632a954063a29231b49" @@ -33,7 +33,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "component": "aredis", "db.system": "redis", "out.host": "localhost", @@ -62,7 +62,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "component": "aredis", "db.system": "redis", "out.host": "localhost", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_long_command.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_long_command.json index cbcd14bcbd6..7b66b1ee6fa 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_long_command.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_long_command.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_opentracing.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_opentracing.json index 9cf2faf87cf..4b92f11d92e 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_opentracing.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_opentracing.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.tid": "654a694400000000", "component": "aredis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_immediate.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_immediate.json index d6a00258957..dd4ddb4f17a 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_immediate.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_immediate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_traced.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_traced.json index 39c23b4b2dd..fa796968608 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_traced.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_pipeline_traced.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.aredis.test_aredis.test_unicode.json b/tests/snapshots/tests.contrib.aredis.test_aredis.test_unicode.json index 718ff1c8599..fc3dffe9e5c 100644 --- a/tests/snapshots/tests.contrib.aredis.test_aredis.test_unicode.json +++ b/tests/snapshots/tests.contrib.aredis.test_aredis.test_unicode.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aredis", diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json index 70a370b0d35..f64347ea749 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "django", @@ -45,7 +45,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -62,7 +62,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -79,7 +79,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -96,7 +96,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -113,7 +113,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -130,7 +130,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -147,7 +147,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -164,7 +164,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -181,7 +181,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -198,7 +198,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -215,7 +215,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -232,7 +232,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -249,7 +249,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -266,7 +266,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -283,7 +283,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -300,7 +300,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -317,7 +317,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -334,7 +334,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -351,7 +351,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -368,7 +368,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -385,7 +385,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -402,7 +402,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -419,7 +419,7 @@ "type": "template", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django", "django.template.engine.class": "django.template.engine.Engine" @@ -437,7 +437,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -454,7 +454,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -471,7 +471,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -488,7 +488,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -505,7 +505,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -522,7 +522,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -539,7 +539,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_callable_view.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_callable_view.json index b464ea62a78..a498faffa93 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_callable_view.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_callable_view.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "django", @@ -45,7 +45,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -62,7 +62,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -79,7 +79,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -96,7 +96,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -113,7 +113,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -130,7 +130,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -147,7 +147,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -164,7 +164,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -181,7 +181,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -198,7 +198,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -215,7 +215,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -232,7 +232,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -249,7 +249,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -266,7 +266,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -283,7 +283,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -300,7 +300,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -317,7 +317,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -334,7 +334,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -351,7 +351,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -368,7 +368,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -385,7 +385,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -402,7 +402,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -419,7 +419,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -436,7 +436,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -453,7 +453,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json index ab628f0b202..9403c178210 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "django", @@ -47,7 +47,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -64,7 +64,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -81,7 +81,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -98,7 +98,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -115,7 +115,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -132,7 +132,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -149,7 +149,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -166,7 +166,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -183,7 +183,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -200,7 +200,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -217,7 +217,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -234,7 +234,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -251,7 +251,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -268,7 +268,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -285,7 +285,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -302,7 +302,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -319,7 +319,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -336,7 +336,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -353,7 +353,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -370,7 +370,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -387,7 +387,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -404,7 +404,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -421,7 +421,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -438,7 +438,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -455,7 +455,7 @@ "type": "template", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django", "django.template.engine.class": "django.template.engine.Engine", @@ -474,7 +474,7 @@ "type": "cache", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django", "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", @@ -496,7 +496,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -513,7 +513,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -530,7 +530,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -547,7 +547,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -564,7 +564,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -581,7 +581,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_streamed_file.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_streamed_file.json index 60f30925b91..2f9439b715c 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_streamed_file.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_streamed_file.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "component": "django", "django.request.class": "django.core.handlers.wsgi.WSGIRequest", @@ -44,7 +44,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 12018000, @@ -60,7 +60,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 322000, @@ -76,7 +76,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 11563000, @@ -92,7 +92,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 270000, @@ -108,7 +108,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 11198000, @@ -124,7 +124,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 26000, @@ -140,7 +140,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 11081000, @@ -156,7 +156,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 22000, @@ -172,7 +172,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 11006000, @@ -188,7 +188,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 1586000, @@ -204,7 +204,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 9328000, @@ -220,7 +220,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 9248000, @@ -236,7 +236,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 16000, @@ -252,7 +252,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 9130000, @@ -268,7 +268,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 9096000, @@ -284,7 +284,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 9045000, @@ -300,7 +300,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 33000, @@ -316,7 +316,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 18000, @@ -332,7 +332,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 299000, @@ -348,7 +348,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 27000, @@ -364,7 +364,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 23000, @@ -380,7 +380,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 15000, @@ -396,7 +396,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 15000, @@ -412,7 +412,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 15000, @@ -428,7 +428,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "component": "django" }, "duration": 21000, diff --git a/tests/snapshots/tests.contrib.django_hosts.test_django.test_django_hosts_request.json b/tests/snapshots/tests.contrib.django_hosts.test_django.test_django_hosts_request.json index 2c7ed914879..6b09581b66f 100644 --- a/tests/snapshots/tests.contrib.django_hosts.test_django.test_django_hosts_request.json +++ b/tests/snapshots/tests.contrib.django_hosts.test_django.test_django_hosts_request.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "django", @@ -45,7 +45,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -62,7 +62,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -79,7 +79,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -96,7 +96,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -113,7 +113,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -130,7 +130,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -147,7 +147,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -164,7 +164,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -181,7 +181,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -198,7 +198,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -215,7 +215,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -232,7 +232,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -249,7 +249,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -266,7 +266,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -283,7 +283,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -300,7 +300,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -317,7 +317,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -334,7 +334,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -351,7 +351,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -368,7 +368,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -385,7 +385,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -402,7 +402,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -419,7 +419,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -436,7 +436,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -453,7 +453,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django_hosts", "_dd.p.tid": "654a694400000000", "component": "django" }, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_exception_no_retries.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_exception_no_retries.json index 0e9c3c07864..b02973135c5 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_exception_no_retries.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_exception_no_retries.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_retry_exception.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_retry_exception.json index e9ad60ba4d6..218eadca619 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_retry_exception.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_retry_exception.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send.json index de42bb216c7..6194693147c 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, @@ -29,7 +29,7 @@ [ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send_with_params.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send_with_params.json index b037c6fad91..d13031cf5a2 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send_with_params.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_fn_task_send_with_params.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, @@ -29,7 +29,7 @@ [ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_idempotent_patch.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_idempotent_patch.json index 2b7521b6891..cec8a33847a 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_idempotent_patch.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_idempotent_patch.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, @@ -29,7 +29,7 @@ [ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_send_exception.json b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_send_exception.json index 399807aa151..aaa0b2dc805 100644 --- a/tests/snapshots/tests.contrib.dramatiq.test_integration.test_send_exception.json +++ b/tests/snapshots/tests.contrib.dramatiq.test_integration.test_send_exception.json @@ -1,7 +1,7 @@ [[ { "name": "dramatiq.Actor.send_with_options", - "service": "", + "service": "tests.contrib.dramatiq", "resource": "dramatiq.Actor.send_with_options", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_500_error_raised.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_500_error_raised.json index e7d9bb89c7d..af5fc72a446 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_500_error_raised.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_500_error_raised.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9000000000", "component": "fastapi", diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_background_task.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_background_task.json index 5d4317b43dc..e4e3064ed10 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_background_task.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_background_task.json @@ -1,7 +1,7 @@ [[ { "name": "fastapi.background_task", - "service": "", + "service": "tests.contrib.fastapi", "resource": "custom_task", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd8b00000000", "component": "fastapi", @@ -68,7 +68,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 22000, "start": 1709362571921182000 diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_subapp_snapshot.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_subapp_snapshot.json index 815f6a3af22..e95db1a3ec4 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_subapp_snapshot.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_subapp_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9000000000", "component": "fastapi", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "component": "fastapi", "http.method": "GET", "http.status_code": "200", @@ -64,7 +64,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 42000, "start": 1709362576025776000 diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_table_query_snapshot.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_table_query_snapshot.json index b600c36f367..cc0e71600d8 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_table_query_snapshot.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_table_query_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9000000000", "component": "fastapi", @@ -42,7 +42,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 83000, "start": 1709362576062154000 @@ -58,7 +58,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9000000000", "component": "fastapi", @@ -91,7 +91,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 62000, "start": 1709362576063633000 diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_traced_websocket.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_traced_websocket.json index 061a28b6714..1c4314562c3 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_traced_websocket.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_traced_websocket.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9800000000", "component": "fastapi", diff --git a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_tracing_in_middleware.json b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_tracing_in_middleware.json index 3c89495367a..406894e83f8 100644 --- a/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_tracing_in_middleware.json +++ b/tests/snapshots/tests.contrib.fastapi.test_fastapi.test_tracing_in_middleware.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.fastapi", "_dd.p.dm": "-0", "_dd.p.tid": "65e2cd9000000000", "component": "fastapi", @@ -42,7 +42,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 359000, "start": 1709362576043450000 @@ -57,7 +57,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.contrib.fastapi" }, "duration": 25000, "start": 1709362576043632000 diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json index a5ffadb4664..77d45d722ad 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json index 2425346e4aa..0a3fdeb85ae 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json index b1617104501..8455611610a 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json index 87fa8ebed7f..6401fe476d4 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json index 73bab941858..785f7ba0877 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json index b9d97a50a97..d53aa9fc4f2 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json index 7871efb1fa1..9acb8b94f64 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json index 08754d32b29..6cec8d60a43 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, @@ -40,7 +40,7 @@ [ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json index f8ca90ac9f5..fdb4115a8bf 100644 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json +++ b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json @@ -1,7 +1,7 @@ [[ { "name": "gemini.request", - "service": "", + "service": "tests.contrib.google_generativeai", "resource": "GenerativeModel.generate_content", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute.json b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute.json index d03344331bb..cbaef2543cd 100644 --- a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute.json +++ b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.type": "query", diff --git a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute_with_resolvers.json b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute_with_resolvers.json index 6e7e61b084b..a3b1795df9e 100644 --- a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute_with_resolvers.json +++ b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_execute_with_resolvers.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.type": "query", @@ -94,7 +94,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql" }, @@ -111,7 +111,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql" }, @@ -128,7 +128,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql" }, @@ -145,7 +145,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql" }, diff --git a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_failing_execute.json b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_failing_execute.json index cb8a1d00f22..6be615d9a44 100644 --- a/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_failing_execute.json +++ b/tests/snapshots/tests.contrib.graphene.test_graphene.test_schema_failing_execute.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -38,7 +38,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -56,7 +56,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ patron { id name age } }" @@ -74,7 +74,7 @@ "type": "graphql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "error.message": "exception was raised in a graphene query\n\nGraphQL request:3:7\n2 | {\n3 | patron {\n | ^\n4 | id", @@ -98,7 +98,7 @@ "type": "graphql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphene", "_dd.p.tid": "654a694400000000", "component": "graphql", "error.message": "exception was raised in a graphene query", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql.json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql.json index e2045fbe853..baa635e0d9c 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql.json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_error.json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_error.json index 017439c394b..4b95ed96c9e 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_error.json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_error.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -38,7 +38,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "{ invalid_schema }" @@ -56,7 +56,7 @@ "type": "graphql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "error.message": "Cannot query field 'invalid_schema' on type 'RootQueryType'.\n\nGraphQL request:1:3\n1 | { invalid_schema }\n | ^", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_execute_with_middleware.json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_execute_with_middleware.json index 7c55b884995..cbc434788b5 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_execute_with_middleware.json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_execute_with_middleware.json @@ -1,7 +1,7 @@ [[ { "name": "test-execute-instrumentation", - "service": "", + "service": "tests.contrib.graphql", "resource": "test-execute-instrumentation", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", @@ -78,7 +78,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql" }, @@ -95,7 +95,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000" }, "duration": 16708, @@ -111,7 +111,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", @@ -135,7 +135,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql" }, @@ -152,7 +152,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000" }, "duration": 14000, diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_document_with_no_location.json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_document_with_no_location.json index 17ff8b63348..6f959c9ec10 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_document_with_no_location.json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_document_with_no_location.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -37,7 +37,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", diff --git a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_traced_resolver.json b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_traced_resolver.json index d2a5ce07045..775adc1a204 100644 --- a/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_traced_resolver.json +++ b/tests/snapshots/tests.contrib.graphql.test_graphql.test_graphql_with_traced_resolver.json @@ -9,7 +9,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "graphql", @@ -36,7 +36,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -54,7 +54,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.source": "query HELLO { hello }" @@ -72,7 +72,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql", "graphql.operation.name": "HELLO", @@ -95,7 +95,7 @@ "type": "graphql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.graphql", "_dd.p.tid": "654a694400000000", "component": "graphql" }, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_configure_service_name.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_configure_service_name.json index 37bd00c8130..8ad8d04c329 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_configure_service_name.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_configure_service_name.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.httpx", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "httpx", diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_200.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_200.json index 3ec1faacc54..ff5fd8ca143 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_200.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_200.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_500.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_500.json index 51d5c64bd45..9ae35ed08a3 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_500.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_get_500.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_request_headers.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_request_headers.json index e74e0c4d10c..f92f444d143 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_request_headers.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_request_headers.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_default.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_default.json index 285fc0ffc9f..14d13084d1d 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_default.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_default.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v0.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v0.json index 428727ef8fa..4f8be41c9e1 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v0.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_schematized_unspecified_service_name_env_v0.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_split_by_domain.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_split_by_domain.json index 9eb4acbc1cb..3b933ab6f5a 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_split_by_domain.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_split_by_domain.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.httpx", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "httpx", diff --git a/tests/snapshots/tests.contrib.httpx.test_httpx.test_trace_query_string.json b/tests/snapshots/tests.contrib.httpx.test_httpx.test_trace_query_string.json index db98c4fe671..939a7dba7fd 100644 --- a/tests/snapshots/tests.contrib.httpx.test_httpx.test_trace_query_string.json +++ b/tests/snapshots/tests.contrib.httpx.test_httpx.test_trace_query_string.json @@ -1,7 +1,7 @@ [[ { "name": "http.request", - "service": "", + "service": "tests.contrib.httpx", "resource": "http.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_ai21_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_ai21_llm_sync.json index bf906812d09..f874cb0c997 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_ai21_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_ai21_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.ai21.AI21", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_llm_sync.json index 505a4cb27e7..50ac99f6b80 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.cohere.Cohere", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_math_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_math_chain.json index 5b53ed64739..955b01e825d 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_math_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_cohere_math_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm_math.base.LLMMathChain", "trace_id": 0, "span_id": 1, @@ -31,7 +31,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -55,7 +55,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.cohere.Cohere", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_document.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_document.json index 1ce0f7e4f17..59dbbd54fd8 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_document.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_document.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.fake.FakeEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_query.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_query.json index 006f390a3bb..27094157302 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_query.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_fake_embedding_query.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.fake.FakeEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_huggingfacehub_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_huggingfacehub_llm_sync.json index d4d41d43de1..5161352b053 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_huggingfacehub_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_huggingfacehub_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.huggingface_hub.HuggingFaceHub", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_call.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_call.json index 8e1eb81bcf8..0eda22db2e6 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_call.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_call.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chat_models.openai.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_generate.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_generate.json index d6e29571d9c..d577c723a89 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_generate.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_generate.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chat_models.openai.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_39.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_39.json index 7b877ddca21..8e1db682c34 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_39.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_39.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chat_models.openai.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_langchain_openai.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_langchain_openai.json index ecb0e57af40..97f062deee8 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_langchain_openai.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_call_langchain_openai.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_generate_39.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_generate_39.json index 7fca1b92ae6..9e3586dbdf8 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_generate_39.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_chat_model_sync_generate_39.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chat_models.openai.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_document.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_document.json index 663eb0e5a46..498fb90b0ed 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_document.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_document.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.openai.OpenAIEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_query.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_query.json index 27f8ea6fb32..0223a8f96ac 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_query.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_embedding_query.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.openai.OpenAIEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_async.json index 9da602d3fb8..bd3e6e31b97 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_error.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_error.json index f8f49aa691e..36a22643a49 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_error.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_error.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync.json index 27868b6a1df..7cd8ad9bb80 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_39.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_39.json index ffb67d28c21..58564950a4a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_39.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_39.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts.json index e93917cc2aa..438d48c7ad2 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts_39.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts_39.json index d899bf71bb0..c9671dc85bb 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts_39.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_llm_sync_multiple_prompts_39.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_math_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_math_chain.json index 8e4fee66411..fa869bf353d 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_math_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_math_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm_math.base.LLMMathChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -63,7 +63,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain.json index 0756d047202..444430e4efb 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -36,7 +36,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.transform.TransformChain", "trace_id": 0, "span_id": 2, @@ -57,7 +57,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -84,7 +84,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_async.json index 7d6414734a6..d46dff4d757 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 4, @@ -96,7 +96,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -122,7 +122,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 5, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_sync.json index 013ded4304a..b9b5ef33eee 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_openai_sequential_chain_with_multiple_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 4, @@ -96,7 +96,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -122,7 +122,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 5, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain.json index 6d4384a0708..c37a59fa5c9 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.qa_with_sources.retrieval.RetrievalQAWithSourcesChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.vectorstores.pinecone.Pinecone", "trace_id": 0, "span_id": 2, @@ -80,7 +80,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.openai.OpenAIEmbeddings", "trace_id": 0, "span_id": 4, @@ -104,7 +104,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.combine_documents.stuff.StuffDocumentsChain", "trace_id": 0, "span_id": 3, @@ -131,7 +131,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 5, @@ -159,7 +159,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 6, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain_39.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain_39.json index 2355aaf2ffe..e2e365bd57d 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain_39.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_retrieval_chain_39.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.qa_with_sources.retrieval.RetrievalQAWithSourcesChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.vectorstores.pinecone.Pinecone", "trace_id": 0, "span_id": 2, @@ -81,7 +81,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.openai.OpenAIEmbeddings", "trace_id": 0, "span_id": 4, @@ -106,7 +106,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.combine_documents.stuff.StuffDocumentsChain", "trace_id": 0, "span_id": 3, @@ -134,7 +134,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 5, @@ -163,7 +163,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.llms.openai.OpenAI", "trace_id": 0, "span_id": 6, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_similarity_search.json b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_similarity_search.json index d3c21b1a5c7..e2707a9ea5b 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_similarity_search.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain.test_pinecone_vectorstore_similarity_search.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.vectorstores.pinecone.Pinecone", "trace_id": 0, "span_id": 1, @@ -40,7 +40,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.embeddings.openai.OpenAIEmbeddings", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_ai21_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_ai21_llm_sync.json index 82442f9abb2..b23b73d1409 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_ai21_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_ai21_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_community.llms.ai21.AI21", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke.json index 97bcba2d7ee..2b7ff2241e9 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "Circumference calculator", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke_non_json_serializable_config.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke_non_json_serializable_config.json index b679d4b785b..4ac7e76b583 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke_non_json_serializable_config.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_base_tool_invoke_non_json_serializable_config.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "Circumference calculator", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_chain_invoke.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_chain_invoke.json index c16c796a6a5..b8e1c37819a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_chain_invoke.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_chain_invoke.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_cohere_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_cohere_llm_sync.json index 85d36538d03..4abd4ac9c43 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_cohere_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_cohere_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_cohere.llms.Cohere", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_faiss_vectorstore_retrieval.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_faiss_vectorstore_retrieval.json index 477574379a9..aaad5aeb7a9 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_faiss_vectorstore_retrieval.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_faiss_vectorstore_retrieval.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.embeddings.base.OpenAIEmbeddings", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ [ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_community.vectorstores.faiss.FAISS", "trace_id": 1, "span_id": 1, @@ -65,7 +65,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.embeddings.base.OpenAIEmbeddings", "trace_id": 1, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_document.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_document.json index 3a8fae94add..b0ba4091fa4 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_document.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_document.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_community.embeddings.fake.FakeEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_query.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_query.json index 24dd06fcb3c..95fa195d2ea 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_query.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_fake_embedding_query.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_community.embeddings.fake.FakeEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_huggingfacehub_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_huggingfacehub_llm_sync.json index 492dd89e7e5..43a52f7f154 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_huggingfacehub_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_huggingfacehub_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_community.llms.huggingface_endpoint.HuggingFaceEndpoint", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcecl_chain_non_dict_input.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcecl_chain_non_dict_input.json index 18396e9bd81..2a25652e285 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcecl_chain_non_dict_input.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcecl_chain_non_dict_input.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch.json index 857933bfe5b..3031a3d2804 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, @@ -69,7 +69,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_311.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_311.json index 51ea2464138..5ff0e2d7bd8 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_311.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_311.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, @@ -69,7 +69,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_async.json index ef507135c57..c43504bac7a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_batch_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, @@ -69,7 +69,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_complicated.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_complicated.json index 33a3ecfd937..26386ef066b 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_complicated.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_complicated.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_nested.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_nested.json index fbd812e6e50..3262cea3b96 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_nested.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_nested.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 4, @@ -94,7 +94,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_non_dict_input.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_non_dict_input.json index 8e3c081fcf1..dcc77d8a33c 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_non_dict_input.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_non_dict_input.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple.json index f05b8fe397e..305bd4e751f 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple_async.json index 336b92e2785..ce966306e68 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_anthropic.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_anthropic.json index e4af1719557..2ac2dcc75f1 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_anthropic.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_anthropic.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_anthropic.chat_models.ChatAnthropic", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_openai.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_openai.json index 4db685e0fca..3e6131fdcb8 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_openai.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_with_tools_openai.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_call.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_call.json index 6749e91a80c..a748cbecd51 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_call.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_call.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_generate.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_generate.json index 99ad139c6cf..5088caae82a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_generate.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_async_generate.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_call_langchain_openai.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_call_langchain_openai.json index 79833a0cb80..269a94e6705 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_call_langchain_openai.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_call_langchain_openai.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_generate.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_generate.json index 3b53614b898..96529bb8b3d 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_generate.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_sync_generate.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_vision_generate.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_vision_generate.json index f4cc580bb7e..89f6dcb9251 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_vision_generate.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_chat_model_vision_generate.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_embedding_query.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_embedding_query.json index 8568c2f1cde..924c715454f 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_embedding_query.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_embedding_query.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.embeddings.base.OpenAIEmbeddings", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_async.json index 64e58d2e178..c82822de607 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_error.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_error.json index dbfb7994df4..31df1b37550 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_error.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_error.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync.json index 43e1f9c4131..1460467119b 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync_multiple_prompts.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync_multiple_prompts.json index 31db3686d54..317a7ab5f21 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync_multiple_prompts.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_llm_sync_multiple_prompts.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain.json index 3331d8497be..666916702e0 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm_math.base.LLMMathChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -63,7 +63,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain_async.json index fd1711bdfc2..76129c4bc3e 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_math_chain_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm_math.base.LLMMathChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -63,7 +63,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain.json index 8f3e44e2b7b..9ed77b47f8a 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -36,7 +36,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.transform.TransformChain", "trace_id": 0, "span_id": 2, @@ -57,7 +57,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -84,7 +84,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_async.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_async.json index 251d83ad5e4..77d4bba6755 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_async.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_async.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 4, @@ -95,7 +95,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -121,7 +121,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 5, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_sync.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_sync.json index c86641ee612..ba77f275645 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_sync.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_openai_sequential_chain_with_multiple_llm_sync.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.sequential.SequentialChain", "trace_id": 0, "span_id": 1, @@ -34,7 +34,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 4, @@ -97,7 +97,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 3, @@ -123,7 +123,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 5, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_retrieval_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_retrieval_chain.json index 89d991a2c3e..e7aa94842af 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_retrieval_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_retrieval_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.qa_with_sources.retrieval.RetrievalQAWithSourcesChain", "trace_id": 0, "span_id": 1, @@ -35,7 +35,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_pinecone.vectorstores.PineconeVectorStore", "trace_id": 0, "span_id": 2, @@ -59,7 +59,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.embeddings.base.OpenAIEmbeddings", "trace_id": 0, "span_id": 4, @@ -83,7 +83,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.combine_documents.stuff.StuffDocumentsChain", "trace_id": 0, "span_id": 3, @@ -110,7 +110,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain.chains.llm.LLMChain", "trace_id": 0, "span_id": 5, @@ -138,7 +138,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 6, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_similarity_search.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_similarity_search.json index 6f2cc59f0e6..22a66a8c0a7 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_similarity_search.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_pinecone_vectorstore_similarity_search.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_pinecone.vectorstores.PineconeVectorStore", "trace_id": 0, "span_id": 1, @@ -37,7 +37,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.embeddings.base.OpenAIEmbeddings", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chain.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chain.json index ad34fcb3343..adec02f3eee 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chain.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chain.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -30,7 +30,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chat.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chat.json index abec415263d..7705ec0697e 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chat.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_chat.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_json_output_parser.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_json_output_parser.json index 9a840eea4bb..6e763167ca7 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_json_output_parser.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_json_output_parser.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_core.runnables.base.RunnableSequence", "trace_id": 0, "span_id": 1, @@ -31,7 +31,7 @@ }, { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.chat_models.base.ChatOpenAI", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_llm.json b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_llm.json index 8b5043f37c4..6a34bbed9fc 100644 --- a/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_llm.json +++ b/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_streamed_llm.json @@ -1,7 +1,7 @@ [[ { "name": "langchain.request", - "service": "", + "service": "tests.contrib.langchain", "resource": "langchain_openai.llms.base.OpenAI", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_acompletion.json b/tests/snapshots/tests.contrib.openai.test_openai.test_acompletion.json index d983e2c374d..3a0cd66dbbf 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_acompletion.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_acompletion.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_atranscribe.json b/tests/snapshots/tests.contrib.openai.test_openai.test_atranscribe.json index 873aee1ecd6..97dee08bbab 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_atranscribe.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_atranscribe.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createTranscription", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_atranslate.json b/tests/snapshots/tests.contrib.openai.test_openai.test_atranslate.json index afee69163ee..c1f2c038888 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_atranslate.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_atranslate.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createTranslation", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_chat_completion.json b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_chat_completion.json index af77738c642..8257b41b636 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_chat_completion.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_chat_completion.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createChatCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_completion.json b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_completion.json index 3160badff25..01fc0431380 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_completion.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_completion.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_embedding.json b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_embedding.json index fc939b11a8d..d1d4b6b5f5b 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_embedding.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_azure_openai_embedding.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createEmbedding", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion.json b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion.json index 591d8982557..adb2bece666 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createChatCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_function_calling.json b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_function_calling.json index c2cfc390c32..701f349187f 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_function_calling.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_function_calling.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createChatCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_image_input.json b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_image_input.json index 226430c2843..8f969030564 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_image_input.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_chat_completion_image_input.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createChatCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_completion.json b/tests/snapshots/tests.contrib.openai.test_openai.test_completion.json index b95432dc631..67f7aa1574e 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_completion.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_completion.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_create_moderation.json b/tests/snapshots/tests.contrib.openai.test_openai.test_create_moderation.json index 01cef45d03f..38c145f1814 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_create_moderation.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_create_moderation.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createModeration", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding.json b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding.json index c388904e1d8..1309915df99 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createEmbedding", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_array_of_token_arrays.json b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_array_of_token_arrays.json index 4fb2d8e39e2..60584a2a1f9 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_array_of_token_arrays.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_array_of_token_arrays.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createEmbedding", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_string_array.json b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_string_array.json index a5dbc00d42c..f9c427d1884 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_string_array.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_string_array.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createEmbedding", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_token_array.json b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_token_array.json index d73df1c6ec5..914f1a5863f 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_token_array.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_embedding_token_array.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createEmbedding", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_file_create.json b/tests/snapshots/tests.contrib.openai.test_openai.test_file_create.json index 8e39c713021..838851052ea 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_file_create.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_file_create.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createFile", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_file_delete.json b/tests/snapshots/tests.contrib.openai.test_openai.test_file_delete.json index ffd4e393cdd..de2826cdba9 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_file_delete.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_file_delete.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "deleteFile", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_file_download.json b/tests/snapshots/tests.contrib.openai.test_openai.test_file_download.json index fe79532f5bd..9cd8b318614 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_file_download.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_file_download.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "downloadFile", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_file_list.json b/tests/snapshots/tests.contrib.openai.test_openai.test_file_list.json index dfe2df6603d..6da8b126d9a 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_file_list.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_file_list.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "listFiles", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_file_retrieve.json b/tests/snapshots/tests.contrib.openai.test_openai.test_file_retrieve.json index dcb15d5aec8..baab55a111e 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_file_retrieve.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_file_retrieve.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "retrieveFile", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_image_b64_json_response.json b/tests/snapshots/tests.contrib.openai.test_openai.test_image_b64_json_response.json index 04a0e4dae29..cf8112be568 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_image_b64_json_response.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_image_b64_json_response.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createImage", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_image_create.json b/tests/snapshots/tests.contrib.openai.test_openai.test_image_create.json index 6a3d28969c5..10c57ccbc3e 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_image_create.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_image_create.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createImage", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit.json b/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit.json index 3cec4aa01d1..4fe6fe9ccb0 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createImageEdit", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit_binary_input.json b/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit_binary_input.json index 1de20f38d5d..9ad4da73f6d 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit_binary_input.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_image_edit_binary_input.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createImageEdit", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_image_variation.json b/tests/snapshots/tests.contrib.openai.test_openai.test_image_variation.json index 47457b22e70..1ea80ab609b 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_image_variation.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_image_variation.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createImageVariation", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_misuse.json b/tests/snapshots/tests.contrib.openai.test_openai.test_misuse.json index 95b53b2c322..67dc45e3d3f 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_misuse.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_misuse.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_model_delete.json b/tests/snapshots/tests.contrib.openai.test_openai.test_model_delete.json index f6cdde2fcaa..c58eff9626a 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_model_delete.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_model_delete.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "deleteModel", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_model_list.json b/tests/snapshots/tests.contrib.openai.test_openai.test_model_list.json index 8079ba3d1f7..6c1decd8122 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_model_list.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_model_list.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "listModels", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_model_retrieve.json b/tests/snapshots/tests.contrib.openai.test_openai.test_model_retrieve.json index 955153a3a5e..37061e9695d 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_model_retrieve.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_model_retrieve.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "retrieveModel", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_span_finish_on_stream_error.json b/tests/snapshots/tests.contrib.openai.test_openai.test_span_finish_on_stream_error.json index 08d74257629..d35ad5e0491 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_span_finish_on_stream_error.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_span_finish_on_stream_error.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createCompletion", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_transcribe.json b/tests/snapshots/tests.contrib.openai.test_openai.test_transcribe.json index 8c09f1f3a92..bdfe0d0f946 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_transcribe.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_transcribe.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createTranscription", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_translate.json b/tests/snapshots/tests.contrib.openai.test_openai.test_translate.json index d81cb52a7a4..2582c536ed1 100644 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_translate.json +++ b/tests/snapshots/tests.contrib.openai.test_openai.test_translate.json @@ -1,7 +1,7 @@ [[ { "name": "openai.request", - "service": "", + "service": "tests.contrib.openai", "resource": "createTranslation", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_with_rate.json index e0d741fc6e1..507d5002670 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_with_rate.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_with_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_without_rate.json index 68929687b86..318db41f310 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_without_rate.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_analytics_without_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_basics.json b/tests/snapshots/tests.contrib.redis.test_redis.test_basics.json index 16c51025235..42bf669a591 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_basics.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_basics.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_custom_cmd_length.json b/tests/snapshots/tests.contrib.redis.test_redis.test_custom_cmd_length.json index ba9b3c6dfea..f95bbfdb9fe 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_custom_cmd_length.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_custom_cmd_length.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_full_command_in_resource_config.json b/tests/snapshots/tests.contrib.redis.test_redis.test_full_command_in_resource_config.json index 26ae24e24fe..3945ec53e47 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_full_command_in_resource_config.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_full_command_in_resource_config.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "component": "redis", "db.system": "redis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "component": "redis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_long_command.json b/tests/snapshots/tests.contrib.redis.test_redis.test_long_command.json index d9e119864a8..2b9a2d93ca5 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_long_command.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_long_command.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_meta_override.json b/tests/snapshots/tests.contrib.redis.test_redis.test_meta_override.json index 8d3c54c7fe8..eabb712796a 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_meta_override.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_meta_override.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "cheese": "camembert", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_opentracing.json b/tests/snapshots/tests.contrib.redis.test_redis.test_opentracing.json index a2bc5c958bd..ae77229102b 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_opentracing.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_opentracing.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_immediate.json b/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_immediate.json index 35bad2a6661..47a77c9ad97 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_immediate.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_immediate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_traced.json b/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_traced.json index a41acb5bacd..63d13253398 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_traced.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_pipeline_traced.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis.test_unicode.json b/tests/snapshots/tests.contrib.redis.test_redis.test_unicode.json index 4ccf8c37b46..be749df1f83 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis.test_unicode.json +++ b/tests/snapshots/tests.contrib.redis.test_redis.test_unicode.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_basic_request.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_basic_request.json index 29ed7b2aeb0..901a9dddbfb 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_basic_request.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_basic_request.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_client_name.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_client_name.json index 740bd5bbc3c..7b9b4cad0f8 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_client_name.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_client_name.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_connection_error.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_connection_error.json index 8a72c250fa8..7638a9b0bcf 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_connection_error.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_connection_error.json @@ -9,7 +9,7 @@ "type": "redis", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_args.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_args.json index 15f64755506..39bf76ff553 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_args.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_args.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_pipeline_args.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_pipeline_args.json index a41375fd6fd..49021ad9afc 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_pipeline_args.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_decoding_non_utf8_pipeline_args.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_long_command.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_long_command.json index 9d7631383d6..469b4598779 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_long_command.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_long_command.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_override_service_name.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_override_service_name.json index 18b72d0f62e..8c88fdecf5b 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_override_service_name.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_override_service_name.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", @@ -46,7 +46,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", @@ -82,7 +82,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_parenting.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_parenting.json index 64d8b47217c..c6f4987c1e7 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_parenting.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_parenting.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", @@ -63,7 +63,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pin.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pin.json index 386f375aa10..38f0e0278df 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pin.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pin.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced.json index 3cfa402d49c..3c086af26c8 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced_context_manager_transaction.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced_context_manager_transaction.json index 202b64f7858..2ff168bc8c8 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced_context_manager_transaction.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_pipeline_traced_context_manager_transaction.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_two_traced_pipelines.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_two_traced_pipelines.json index 86f35296e3c..3e00f7e5126 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_two_traced_pipelines.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_two_traced_pipelines.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", @@ -63,7 +63,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.tid": "654a694400000000", "component": "redis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_unicode_request.json b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_unicode_request.json index d7681407836..3baeb5a8cf3 100644 --- a/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_unicode_request.json +++ b/tests/snapshots/tests.contrib.redis.test_redis_asyncio.test_unicode_request.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.redis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "redis", diff --git a/tests/snapshots/tests.contrib.rediscluster.test.test_cmd_max_length.json b/tests/snapshots/tests.contrib.rediscluster.test.test_cmd_max_length.json index 7685a200b72..85907ea82f6 100644 --- a/tests/snapshots/tests.contrib.rediscluster.test.test_cmd_max_length.json +++ b/tests/snapshots/tests.contrib.rediscluster.test.test_cmd_max_length.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rediscluster", @@ -41,7 +41,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rediscluster", diff --git a/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_config[True].json b/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_config[True].json index 8f6d830336a..7fbe026f26c 100644 --- a/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_config[True].json +++ b/tests/snapshots/tests.contrib.rediscluster.test.test_full_command_in_resource_config[True].json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "_dd.p.dm": "-0", "component": "rediscluster", "db.system": "redis", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "_dd.p.dm": "-0", "language": "python", "runtime-id": "1e05369c917e47d6a0c31264f74a62b3" @@ -64,7 +64,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "component": "rediscluster", "db.system": "redis", "redis.raw_command": "GET put_key_in_resource", @@ -89,7 +89,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rediscluster", "component": "rediscluster", "db.system": "redis", "redis.raw_command": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_None.json b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_None.json index a2f2bcda6e5..7a548d91093 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_None.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_None.json @@ -58,7 +58,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_custom-worker-service.json b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_custom-worker-service.json index 3f7d666759d..b690a6a7d8a 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_custom-worker-service.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_False_worker_service_custom-worker-service.json @@ -56,7 +56,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_None.json b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_None.json index 51d35c2b586..a0befaff75f 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_None.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_None.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_custom-worker-service.json b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_custom-worker-service.json index 17b558203dc..a8aaf123f38 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_custom-worker-service.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_enqueue_distributed_tracing_enabled_None_worker_service_custom-worker-service.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job.json b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job.json index 5a261478aba..9c4c5a894b7 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "error", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job_pre_1_10_1.json b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job_pre_1_10_1.json index 439e6e95653..032b1ee2c44 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job_pre_1_10_1.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_failing_job_pre_1_10_1.json @@ -9,7 +9,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -42,7 +42,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "error", diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_pin_service.json b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_pin_service.json index f05fd85c425..a99105d6bf8 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_queue_pin_service.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_queue_pin_service.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "3e9522f8-6632-4237-b4ce-43187fe4d82e" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_queue_enqueue.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_queue_enqueue.json index 4cdc0ac3471..c34d7729425 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_queue_enqueue.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_queue_enqueue.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "53d8bd79-f439-4b6e-b111-89d75b915bcd" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker.json index f27bcfaf75e..fef7f4c4523 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "30c861b5-640d-4d34-aed4-f4a5270723f1" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_config_service.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_config_service.json index 3bc1936f422..bfbc6993f4e 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_config_service.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_config_service.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "97f1d8cc-a944-48c9-a71f-2f1f316d0a19" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_multiple_jobs.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_multiple_jobs.json index 267d3a539bc..acfbe56c677 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_multiple_jobs.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_multiple_jobs.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "45ed1c2f-0e79-4a7a-9ca6-2d124eb0d43b" @@ -90,7 +90,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -120,7 +120,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -152,7 +152,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "0d125a3e-a0b7-4fc1-bff0-96c4bd7e9d09" @@ -171,7 +171,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -201,7 +201,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -233,7 +233,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "136e8c75-89b9-4c73-9894-ed1b61420ab0" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_pin_service.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_pin_service.json index 63652273884..30918a04bf5 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_pin_service.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_pin_service.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "18261607-4d5c-4b8a-a745-5af5f5093ac2" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_ttl.json b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_ttl.json index 6a764de8aa9..0426fb379ac 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_ttl.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_sync_worker_ttl.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "30c861b5-640d-4d34-aed4-f4a5270723f1" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_worker_class_job.json b/tests/snapshots/tests.contrib.rq.test_rq.test_worker_class_job.json index e18b47c0984..ca71d2b7755 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_worker_class_job.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_worker_class_job.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "163cbef5-7bef-4712-b7f6-3312801605ec" @@ -90,7 +90,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -120,7 +120,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -152,7 +152,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "job.id": "b46b05f7-e418-4cb7-afa8-de25d42ba0bb" diff --git a/tests/snapshots/tests.contrib.rq.test_rq.test_worker_failing_job.json b/tests/snapshots/tests.contrib.rq.test_rq.test_worker_failing_job.json index faa2bcbb7ca..6f1d62170f1 100644 --- a/tests/snapshots/tests.contrib.rq.test_rq.test_worker_failing_job.json +++ b/tests/snapshots/tests.contrib.rq.test_rq.test_worker_failing_job.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -39,7 +39,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "rq", @@ -71,7 +71,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.rq", "_dd.p.tid": "654a694400000000", "component": "rq", "error.message": "error", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_commit.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_commit.json index 29fb4d09482..c9c36bcab42 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_commit.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_commit.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767800000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_executemany_insert.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_executemany_insert.json index 5b8458925cd..cd6b31e3eab 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_executemany_insert.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_executemany_insert.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767500000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall.json index a09a4d12fc3..608e2fafb20 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767800000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall_multiple_rows.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall_multiple_rows.json index c097df9375a..4da8ed175f3 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall_multiple_rows.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchall_multiple_rows.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767b00000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchone.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchone.json index 06c288e73d4..0cacbba1380 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchone.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_fetchone.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767000000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_executemany_insert.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_executemany_insert.json index d8c7393950e..47895b06fe0 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_executemany_insert.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_executemany_insert.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767100000000", "language": "python", @@ -34,7 +34,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "component": "snowflake", "db.application": "PythonConnector", "db.name": "mock-db-name", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall.json index bfbed86cb5b..c4987aef6a2 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2766300000000", "language": "python", @@ -34,7 +34,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "component": "snowflake", "db.application": "PythonConnector", "db.name": "mock-db-name", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall_multiple_rows.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall_multiple_rows.json index 19ad2e8ad3d..8b0b222996c 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall_multiple_rows.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchall_multiple_rows.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2766c00000000", "language": "python", @@ -34,7 +34,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "component": "snowflake", "db.application": "PythonConnector", "db.name": "mock-db-name", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchone.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchone.json index da0ee12cd12..7d5129de97d 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchone.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_ot_fetchone.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767300000000", "language": "python", @@ -34,7 +34,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "component": "snowflake", "db.application": "PythonConnector", "db.name": "mock-db-name", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_pin_override.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_pin_override.json index 6500664fa8a..5a7866739c9 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_pin_override.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_pin_override.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2766f00000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_rollback.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_rollback.json index c5bc31b0218..53f55bfedc3 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_rollback.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_rollback.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2766f00000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_settings_override.json b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_settings_override.json index 4c172099945..df5d48b97da 100644 --- a/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_settings_override.json +++ b/tests/snapshots/tests.contrib.snowflake.test_snowflake.test_snowflake_settings_override.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.snowflake", "_dd.p.dm": "-0", "_dd.p.tid": "66b2767b00000000", "component": "snowflake", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_background_task.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_background_task.json index 3c6cdd64d99..094c5aaf0c2 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_background_task.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_background_task.json @@ -1,7 +1,7 @@ [[ { "name": "starlette.background_task", - "service": "", + "service": "tests.contrib.starlette", "resource": "", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "starlette.background_task", - "service": "", + "service": "tests.contrib.starlette", "resource": "custom_task", "trace_id": 1, "span_id": 1, @@ -61,7 +61,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "660a332700000000", "component": "starlette", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_call_snapshot.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_call_snapshot.json index 7c806c4b8dc..351d70ca476 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_call_snapshot.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_call_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e89a3b00000000", "component": "starlette", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "starlette", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_snapshot.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_snapshot.json index ffd8fc2acd0..dfbd5ba0a38 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_snapshot.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_nested_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e89a3b00000000", "component": "starlette", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "starlette", "http.method": "GET", "http.status_code": "200", @@ -64,7 +64,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "starlette", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_snapshot.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_snapshot.json index aaa58931046..e280fecbc98 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_snapshot.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e7e27300000000", "component": "starlette", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "starlette", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_two_snapshot.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_two_snapshot.json index 9e0d429ef81..0b2d37a54c9 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_two_snapshot.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_subapp_two_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e89a3b00000000", "component": "starlette", @@ -42,7 +42,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "starlette", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.contrib.starlette.test_starlette.test_table_query_snapshot.json b/tests/snapshots/tests.contrib.starlette.test_starlette.test_table_query_snapshot.json index 44a6e417729..f78c5ac20d5 100644 --- a/tests/snapshots/tests.contrib.starlette.test_starlette.test_table_query_snapshot.json +++ b/tests/snapshots/tests.contrib.starlette.test_starlette.test_table_query_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e7e27f00000000", "component": "starlette", @@ -42,7 +42,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "sqlalchemy", "span.kind": "client", "sql.db": "test.db" @@ -66,7 +66,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "_dd.p.dm": "-0", "_dd.p.tid": "65e7e27f00000000", "component": "starlette", @@ -99,7 +99,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.starlette", "component": "sqlalchemy", "span.kind": "client", "sql.db": "test.db" diff --git a/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_connectionpool_snapshot.json b/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_connectionpool_snapshot.json index 2a39102770a..668f22df563 100644 --- a/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_connectionpool_snapshot.json +++ b/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_connectionpool_snapshot.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.urllib3", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "urllib3", diff --git a/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_poolmanager_snapshot.json b/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_poolmanager_snapshot.json index 705d7a2012b..0ca0c11e758 100644 --- a/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_poolmanager_snapshot.json +++ b/tests/snapshots/tests.contrib.urllib3.test_urllib3.test_urllib3_poolmanager_snapshot.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.urllib3", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "urllib3", diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_200.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_200.json index f56eed9bf93..86420a3087d 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_200.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_200.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_500_py3.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_500_py3.json index 82c004d41c6..cdd953ec1c8 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_500_py3.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_500_py3.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -41,7 +41,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "error.message": "Oops!", diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_base_exception_in_wsgi_app_py3.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_base_exception_in_wsgi_app_py3.json index 78f8f558369..a4e84cedbde 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_base_exception_in_wsgi_app_py3.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_base_exception_in_wsgi_app_py3.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -41,7 +41,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "error.message": "base exception raised in wsgi app", diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_chunked.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_chunked.json index 4f76b1e8a37..43346418dc0 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_chunked.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_chunked.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "generator" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_distributed_tracing_nested.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_distributed_tracing_nested.json index 4c8ea687e12..0da573649c6 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_distributed_tracing_nested.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_distributed_tracing_nested.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-3", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "http.method": "GET", @@ -79,7 +79,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -96,7 +96,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -114,7 +114,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list" @@ -132,7 +132,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -150,7 +150,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list_iterator" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_generator_exit_ignored_snapshot.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_generator_exit_ignored_snapshot.json index 30b37a48911..2fde4a9eafb 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_generator_exit_ignored_snapshot.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_generator_exit_ignored_snapshot.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "generator" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_stop_iteration_in_wsgi_app_py3.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_stop_iteration_in_wsgi_app_py3.json index ff654d99340..fe385a3de0c 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_stop_iteration_in_wsgi_app_py3.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_stop_iteration_in_wsgi_app_py3.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "669187f500000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "component": "wsgi" }, "duration": 2339998959, @@ -56,7 +56,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "component": "wsgi", "span.kind": "server" }, @@ -73,7 +73,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "component": "wsgi", "result_class": "tuple" }, diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware.json index b75f82d3369..163351f1966 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -39,7 +39,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "app_tag": "app test tag set", "component": "wsgi" @@ -60,7 +60,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "response_tag": "resp test tag set" diff --git a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware_500.json b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware_500.json index f0c1a4859d5..2bb31c12926 100644 --- a/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware_500.json +++ b/tests/snapshots/tests.contrib.wsgi.test_wsgi.test_wsgi_base_middleware_500.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -41,7 +41,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.wsgi", "_dd.p.tid": "654a694400000000", "component": "wsgi", "error.message": "Oops!", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_with_rate.json index b6d51f33694..2a94b63ab1c 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_with_rate.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_with_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_without_rate.json index 85f43c3383a..40703c30619 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_without_rate.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_analytics_without_rate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_basics.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_basics.json index 286ad8836b6..64a0dc8fcee 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_basics.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_basics.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_cmd_max_length.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_cmd_max_length.json index 2540fedeaf2..b39a54205e8 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_cmd_max_length.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_cmd_max_length.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_config[True].json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_config[True].json index cfd6b2c0cfe..f453d45ceb2 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_config[True].json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_full_command_in_resource_config[True].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "language": "python", "runtime-id": "8684af00a9414982b4794ddcadcd26ec" @@ -33,7 +33,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "component": "yaaredis", "db.system": "redis", "out.host": "localhost", @@ -62,7 +62,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "component": "yaaredis", "db.system": "redis", "out.host": "localhost", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_long_command.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_long_command.json index 19d289698fc..c21f8fc51f5 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_long_command.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_long_command.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_opentracing.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_opentracing.json index 35acd5ad51e..ccee94088be 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_opentracing.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_opentracing.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "language": "python", @@ -34,7 +34,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.tid": "654a694400000000", "component": "yaaredis", "db.system": "redis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_immediate.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_immediate.json index 96b4e6cdda3..d8843f3ac0c 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_immediate.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_immediate.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", @@ -45,7 +45,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_traced.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_traced.json index d3127d2679e..945d5fc508d 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_traced.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_pipeline_traced.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_unicode.json b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_unicode.json index d07afc4bcc1..cb687c7f283 100644 --- a/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_unicode.json +++ b/tests/snapshots/tests.contrib.yaaredis.test_yaaredis.test_unicode.json @@ -9,7 +9,7 @@ "type": "redis", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.yaaredis", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "yaaredis", diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_filters.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_filters.json index 2f2b21e5790..f49585f9318 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_filters.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_filters.json @@ -1,7 +1,7 @@ [[ { "name": "root", - "service": "", + "service": "tests.integration", "resource": "root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_multiple_traces.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_multiple_traces.json index 4e89e4062db..bb0773bf02d 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_multiple_traces.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_multiple_traces.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "k": "v", "language": "python", @@ -37,7 +37,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "duration": 8792, "start": 1692900308663749377 @@ -53,7 +53,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "k": "v", "language": "python", @@ -81,7 +81,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "duration": 7417, "start": 1692900308664070169 diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_sampling.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_sampling.json index 901aafe1056..f50ea82b9fa 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_sampling.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_sampling.json @@ -1,7 +1,7 @@ [[ { "name": "trace7", - "service": "", + "service": "tests.integration", "resource": "trace7", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, @@ -38,7 +38,7 @@ [ { "name": "trace6", - "service": "", + "service": "tests.integration", "resource": "trace6", "trace_id": 1, "span_id": 1, @@ -61,7 +61,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 1, "span_id": 2, @@ -74,7 +74,7 @@ [ { "name": "trace5", - "service": "", + "service": "tests.integration", "resource": "trace5", "trace_id": 2, "span_id": 1, @@ -97,7 +97,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 2, "span_id": 2, @@ -110,7 +110,7 @@ [ { "name": "trace4", - "service": "", + "service": "tests.integration", "resource": "trace4", "trace_id": 3, "span_id": 1, @@ -134,7 +134,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 3, "span_id": 2, @@ -147,7 +147,7 @@ [ { "name": "trace3", - "service": "", + "service": "tests.integration", "resource": "trace3", "trace_id": 4, "span_id": 1, @@ -170,7 +170,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 4, "span_id": 2, @@ -183,7 +183,7 @@ [ { "name": "trace2", - "service": "", + "service": "tests.integration", "resource": "trace2", "trace_id": 5, "span_id": 1, @@ -207,7 +207,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 5, "span_id": 2, @@ -220,7 +220,7 @@ [ { "name": "trace1", - "service": "", + "service": "tests.integration", "resource": "trace1", "trace_id": 6, "span_id": 1, @@ -243,7 +243,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 6, "span_id": 2, diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.4].json b/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.4].json index db260b1f3f6..c2378c88bb2 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.4].json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.4].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "env": "my-env", "language": "python", diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.5].json b/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.5].json index 3741b20568a..f857abb3309 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.5].json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_setting_span_tags_and_metrics_generates_no_error_logs[v0.5].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "env": "my-env", "language": "python", diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_single_trace_single_span.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_single_trace_single_span.json index 24e492df21a..6488442669c 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_single_trace_single_span.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_single_trace_single_span.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "k": "v", "language": "python", diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_synchronous_writer.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_synchronous_writer.json index 7ad0c78c24d..aa2c70ace7c 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_synchronous_writer.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_synchronous_writer.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "language": "python", "runtime-id": "f335963d0a054add88871c4f52e100e1" @@ -33,7 +33,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "duration": 8584, "start": 1692900308688433877 @@ -49,7 +49,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "language": "python", "runtime-id": "f335963d0a054add88871c4f52e100e1" @@ -73,7 +73,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "duration": 15167, "start": 1692900308687627002 diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_trace_with_wrong_metrics_types_not_sent.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_trace_with_wrong_metrics_types_not_sent.json index ee8853d06f9..a1a67aeefc8 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_trace_with_wrong_metrics_types_not_sent.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_trace_with_wrong_metrics_types_not_sent.json @@ -1,7 +1,7 @@ [[ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_multiple_popens.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_multiple_popens.json index d5e493b67fc..1ce299d01d1 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_multiple_popens.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_multiple_popens.json @@ -1,7 +1,7 @@ [[ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child1", - "service": "", + "service": "tests.integration", "resource": "child1", "trace_id": 0, "span_id": 2, @@ -46,7 +46,7 @@ }, { "name": "child2", - "service": "", + "service": "tests.integration", "resource": "child2", "trace_id": 0, "span_id": 3, diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_popen.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_popen.json index 6e843abd8f1..1fa4c547739 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_popen.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracer_trace_across_popen.json @@ -1,7 +1,7 @@ [[ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 0, "span_id": 1, @@ -24,7 +24,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracetagsprocessor_only_adds_new_tags.json b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracetagsprocessor_only_adds_new_tags.json index c87d41556e5..9298b2342cd 100644 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_tracetagsprocessor_only_adds_new_tags.json +++ b/tests/snapshots/tests.integration.test_integration_snapshots.test_tracetagsprocessor_only_adds_new_tags.json @@ -1,7 +1,7 @@ [[ { "name": "web.request", - "service": "", + "service": "tests.integration", "resource": "web.request", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_keep.json b/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_keep.json index a4a3d341c57..1ae5c51e702 100644 --- a/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_keep.json +++ b/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_keep.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-1", "_dd.p.tid": "656a1a2b00000000", "language": "python", @@ -28,7 +28,7 @@ [ { "name": "", - "service": "", + "service": "tests.integration", "resource": "", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_reject.json b/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_reject.json index f58f225bd30..7629d46ca12 100644 --- a/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_reject.json +++ b/tests/snapshots/tests.integration.test_priority_sampling.test_agent_sample_rate_reject.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-1", "_dd.p.tid": "656a1a2b00000000", "language": "python", @@ -28,7 +28,7 @@ [ { "name": "", - "service": "", + "service": "tests.integration", "resource": "", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_propagation.test_sampling_decision_downstream.json b/tests/snapshots/tests.integration.test_propagation.test_sampling_decision_downstream.json index 67ad90cc0d7..53773d8d0c5 100644 --- a/tests/snapshots/tests.integration.test_propagation.test_sampling_decision_downstream.json +++ b/tests/snapshots/tests.integration.test_propagation.test_sampling_decision_downstream.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-4", "language": "python", "runtime-id": "f335963d0a054add88871c4f52e100e1" diff --git a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer0].json b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer0].json index bf1e2519d6d..562690cecef 100644 --- a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer0].json +++ b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer0].json @@ -1,7 +1,7 @@ [[ { "name": "p", - "service": "", + "service": "tests.integration", "resource": "p", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "c1", - "service": "", + "service": "tests.integration", "resource": "c1", "trace_id": 0, "span_id": 2, @@ -40,7 +40,7 @@ }, { "name": "c2", - "service": "", + "service": "tests.integration", "resource": "c2", "trace_id": 0, "span_id": 3, @@ -55,7 +55,7 @@ }, { "name": "gc", - "service": "", + "service": "tests.integration", "resource": "gc", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer1].json b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer1].json index 945373e125f..7aab49428ec 100644 --- a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer1].json +++ b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer1].json @@ -1,7 +1,7 @@ [[ { "name": "p", - "service": "", + "service": "tests.integration", "resource": "p", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "c1", - "service": "", + "service": "tests.integration", "resource": "c1", "trace_id": 0, "span_id": 2, @@ -40,7 +40,7 @@ }, { "name": "c2", - "service": "", + "service": "tests.integration", "resource": "c2", "trace_id": 0, "span_id": 3, @@ -55,7 +55,7 @@ }, { "name": "gc", - "service": "", + "service": "tests.integration", "resource": "gc", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer2].json b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer2].json index 5f4d7c356cf..43e3251e6e8 100644 --- a/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer2].json +++ b/tests/snapshots/tests.integration.test_propagation.test_trace_tags_multispan[tracer2].json @@ -1,7 +1,7 @@ [[ { "name": "p", - "service": "", + "service": "tests.integration", "resource": "p", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ }, { "name": "c1", - "service": "", + "service": "tests.integration", "resource": "c1", "trace_id": 0, "span_id": 2, @@ -48,7 +48,7 @@ }, { "name": "c2", - "service": "", + "service": "tests.integration", "resource": "c2", "trace_id": 0, "span_id": 3, @@ -63,7 +63,7 @@ }, { "name": "gc", - "service": "", + "service": "tests.integration", "resource": "gc", "trace_id": 0, "span_id": 4, diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_measured_span.json b/tests/snapshots/tests.integration.test_trace_stats.test_measured_span.json index 1be565f7b17..70166db7303 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_measured_span.json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_measured_span.json @@ -1,7 +1,7 @@ [[ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 0, "span_id": 2, @@ -38,7 +38,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 1, "span_id": 1, @@ -62,7 +62,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 1, "span_id": 2, @@ -75,7 +75,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 2, "span_id": 1, @@ -99,7 +99,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 2, "span_id": 2, @@ -112,7 +112,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 3, "span_id": 1, @@ -136,7 +136,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 3, "span_id": 2, @@ -149,7 +149,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 4, "span_id": 1, @@ -173,7 +173,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 4, "span_id": 2, @@ -186,7 +186,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 5, "span_id": 1, @@ -210,7 +210,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 5, "span_id": 2, @@ -223,7 +223,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 6, "span_id": 1, @@ -247,7 +247,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 6, "span_id": 2, @@ -260,7 +260,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 7, "span_id": 1, @@ -284,7 +284,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 7, "span_id": 2, @@ -297,7 +297,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 8, "span_id": 1, @@ -321,7 +321,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 8, "span_id": 2, @@ -334,7 +334,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 9, "span_id": 1, @@ -358,7 +358,7 @@ }, { "name": "child", - "service": "", + "service": "tests.integration", "resource": "child", "trace_id": 9, "span_id": 2, @@ -371,7 +371,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 10, "span_id": 1, @@ -395,7 +395,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 10, "span_id": 2, @@ -411,7 +411,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 11, "span_id": 1, @@ -435,7 +435,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 11, "span_id": 2, @@ -451,7 +451,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 12, "span_id": 1, @@ -475,7 +475,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 12, "span_id": 2, @@ -491,7 +491,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 13, "span_id": 1, @@ -515,7 +515,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 13, "span_id": 2, @@ -531,7 +531,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 14, "span_id": 1, @@ -555,7 +555,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 14, "span_id": 2, @@ -571,7 +571,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 15, "span_id": 1, @@ -595,7 +595,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 15, "span_id": 2, @@ -611,7 +611,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 16, "span_id": 1, @@ -635,7 +635,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 16, "span_id": 2, @@ -651,7 +651,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 17, "span_id": 1, @@ -675,7 +675,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 17, "span_id": 2, @@ -691,7 +691,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 18, "span_id": 1, @@ -715,7 +715,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 18, "span_id": 2, @@ -731,7 +731,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.integration", "resource": "parent", "trace_id": 19, "span_id": 1, @@ -755,7 +755,7 @@ }, { "name": "child_stats", - "service": "", + "service": "tests.integration", "resource": "child_stats", "trace_id": 19, "span_id": 2, diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[0.0].json b/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[0.0].json index 07e5f223227..bf83155098c 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[0.0].json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[0.0].json @@ -1,7 +1,7 @@ [[ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 1, "span_id": 1, @@ -51,7 +51,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 2, "span_id": 1, @@ -76,7 +76,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 3, "span_id": 1, @@ -101,7 +101,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 4, "span_id": 1, @@ -126,7 +126,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 5, "span_id": 1, @@ -151,7 +151,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 6, "span_id": 1, @@ -176,7 +176,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 7, "span_id": 1, @@ -201,7 +201,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 8, "span_id": 1, @@ -226,7 +226,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 9, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[1.0].json b/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[1.0].json index a09577cfe72..6e3752bf36f 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[1.0].json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_sampling_rate[1.0].json @@ -1,7 +1,7 @@ [[ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 0, "span_id": 1, @@ -26,7 +26,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 1, "span_id": 1, @@ -51,7 +51,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 2, "span_id": 1, @@ -76,7 +76,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 3, "span_id": 1, @@ -101,7 +101,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 4, "span_id": 1, @@ -126,7 +126,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 5, "span_id": 1, @@ -151,7 +151,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 6, "span_id": 1, @@ -176,7 +176,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 7, "span_id": 1, @@ -201,7 +201,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 8, "span_id": 1, @@ -226,7 +226,7 @@ [ { "name": "operation", - "service": "", + "service": "tests.integration", "resource": "operation", "trace_id": 9, "span_id": 1, diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule0].json b/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule0].json index 7240f479af5..b9c35e6d695 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule0].json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule0].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-3", "_dd.p.tid": "6670641f00000000", "language": "python" diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule1].json b/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule1].json index bdc1f15cc14..03e452b328f 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule1].json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_single_span_sampling[sampling_rule1].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-3", "_dd.p.tid": "6670641f00000000", "language": "python", @@ -36,7 +36,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.span_sampling.mechanism": 8 diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_stats_30.json b/tests/snapshots/tests.integration.test_trace_stats.test_stats_30.json index e827959fb2e..94a2569d67a 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_stats_30.json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_stats_30.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -35,7 +35,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -61,7 +61,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -87,7 +87,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -113,7 +113,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -139,7 +139,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -165,7 +165,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -191,7 +191,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -217,7 +217,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -243,7 +243,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -269,7 +269,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -295,7 +295,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -321,7 +321,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -347,7 +347,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -373,7 +373,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -399,7 +399,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -425,7 +425,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -451,7 +451,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -477,7 +477,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -503,7 +503,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -529,7 +529,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -555,7 +555,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -581,7 +581,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -607,7 +607,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -633,7 +633,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -659,7 +659,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -685,7 +685,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -711,7 +711,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -737,7 +737,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", @@ -763,7 +763,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670647500000000", "language": "python", diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_stats_aggrs.json b/tests/snapshots/tests.integration.test_trace_stats.test_stats_aggrs.json index d2c1a1622a4..ddb0bb4a3af 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_stats_aggrs.json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_stats_aggrs.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "language": "python", @@ -35,7 +35,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.origin": "synthetics", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", @@ -62,7 +62,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "http.status_code": "200", @@ -89,7 +89,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "language": "python", @@ -115,7 +115,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "language": "python", @@ -141,7 +141,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "language": "python", @@ -167,7 +167,7 @@ "type": "db", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670642000000000", "language": "python", diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_stats_errors.json b/tests/snapshots/tests.integration.test_trace_stats.test_stats_errors.json index e1003ff394b..67b72fd6317 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_stats_errors.json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_stats_errors.json @@ -9,7 +9,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -35,7 +35,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -61,7 +61,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -87,7 +87,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -113,7 +113,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -139,7 +139,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -165,7 +165,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -191,7 +191,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -217,7 +217,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -243,7 +243,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -269,7 +269,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -295,7 +295,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -321,7 +321,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -347,7 +347,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -373,7 +373,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -399,7 +399,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -425,7 +425,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -451,7 +451,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -477,7 +477,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -503,7 +503,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -529,7 +529,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -555,7 +555,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -581,7 +581,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -607,7 +607,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -633,7 +633,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -659,7 +659,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -685,7 +685,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -711,7 +711,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -737,7 +737,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", @@ -763,7 +763,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670649800000000", "language": "python", diff --git a/tests/snapshots/tests.integration.test_trace_stats.test_top_level.json b/tests/snapshots/tests.integration.test_trace_stats.test_top_level.json index 7bb8372cf33..92b45c7b5b1 100644 --- a/tests/snapshots/tests.integration.test_trace_stats.test_top_level.json +++ b/tests/snapshots/tests.integration.test_trace_stats.test_top_level.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -34,7 +34,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -53,7 +53,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -78,7 +78,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -97,7 +97,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -122,7 +122,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -141,7 +141,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -166,7 +166,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -185,7 +185,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -210,7 +210,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -229,7 +229,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -254,7 +254,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -273,7 +273,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -298,7 +298,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -317,7 +317,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -342,7 +342,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -361,7 +361,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -386,7 +386,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -405,7 +405,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -430,7 +430,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -449,7 +449,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -474,7 +474,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -493,7 +493,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -518,7 +518,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -537,7 +537,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -562,7 +562,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -581,7 +581,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -606,7 +606,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -625,7 +625,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -650,7 +650,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -669,7 +669,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -694,7 +694,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -713,7 +713,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -738,7 +738,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -757,7 +757,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -782,7 +782,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -801,7 +801,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -826,7 +826,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -845,7 +845,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -870,7 +870,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -889,7 +889,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -914,7 +914,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -933,7 +933,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -958,7 +958,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -977,7 +977,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1002,7 +1002,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1021,7 +1021,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1046,7 +1046,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1065,7 +1065,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1090,7 +1090,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1109,7 +1109,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1134,7 +1134,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1153,7 +1153,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1178,7 +1178,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1197,7 +1197,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1222,7 +1222,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1241,7 +1241,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1266,7 +1266,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 @@ -1285,7 +1285,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.integration", "_dd.p.dm": "-0", "_dd.p.tid": "6670655700000000", "language": "python", @@ -1310,7 +1310,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "" + "_dd.base_service": "tests.integration" }, "metrics": { "_dd.top_level": 1 diff --git a/tests/tracer/runtime/test_runtime_metrics.py b/tests/tracer/runtime/test_runtime_metrics.py index 399b536625a..9039da147a5 100644 --- a/tests/tracer/runtime/test_runtime_metrics.py +++ b/tests/tracer/runtime/test_runtime_metrics.py @@ -46,7 +46,7 @@ def test_one_tag(self): with self.override_global_tracer(): with self.trace("test", service="test"): tags = [k for (k, v) in RuntimeTags(enabled=[SERVICE])] - self.assertEqual(tags, [SERVICE]) + self.assertEqual(set(tags), set([SERVICE])) def test_env_tag(self): def filter_only_env_tags(tags): diff --git a/tests/tracer/test_correlation_log_context.py b/tests/tracer/test_correlation_log_context.py index 0522a042395..73b21443fbc 100644 --- a/tests/tracer/test_correlation_log_context.py +++ b/tests/tracer/test_correlation_log_context.py @@ -1,17 +1,11 @@ import pytest -import structlog from ddtrace import Tracer from ddtrace import config from ddtrace import tracer -from ddtrace._trace.context import Context -from ddtrace._trace.provider import _DD_CONTEXTVAR -from ddtrace.opentracer.tracer import Tracer as OT_Tracer -from tests.utils import override_global_config -@pytest.fixture -def global_config(): +def global_config(config): config.service = "test-service" config.env = "test-env" config.version = "test-version" @@ -36,9 +30,15 @@ def format_trace_id(span): return str(span._trace_id_64bits) -class TestCorrelationLogsContext(object): - def test_get_log_correlation_service(self, global_config): - """Ensure expected DDLogRecord service is generated via get_correlation_log_record.""" +@pytest.mark.subprocess() +def test_get_log_correlation_service(): + """Ensure expected DDLogRecord service is generated via get_correlation_log_record.""" + from ddtrace import Tracer + from ddtrace import tracer + from tests.tracer.test_correlation_log_context import format_trace_id + from tests.utils import override_global_config + + with override_global_config(dict(service="test-service", env="test-env", version="test-version")): with tracer.trace("test-span-1", service="span-service") as span1: dd_log_record = tracer.get_log_correlation_context() assert dd_log_record == { @@ -60,8 +60,16 @@ def test_get_log_correlation_service(self, global_config): "version": "test-version", } - def test_get_log_correlation_context_basic(self, global_config): - """Ensure expected DDLogRecord is generated via get_correlation_log_record.""" + +@pytest.mark.subprocess() +def test_get_log_correlation_context_basic(): + """Ensure expected DDLogRecord is generated via get_correlation_log_record.""" + from ddtrace import Tracer + from ddtrace.context import Context + from tests.tracer.test_correlation_log_context import format_trace_id + from tests.utils import override_global_config + + with override_global_config(dict(service="test-service", env="test-env", version="test-version")): tracer = Tracer() with tracer.trace("test-span-1") as span1: dd_log_record = tracer.get_log_correlation_context() @@ -71,7 +79,7 @@ def test_get_log_correlation_context_basic(self, global_config): "service": "test-service", "env": "test-env", "version": "test-version", - } + }, dd_log_record test_tracer = Tracer() with test_tracer.trace("test-span-2") as span2: dd_log_record = test_tracer.get_log_correlation_context() @@ -81,7 +89,7 @@ def test_get_log_correlation_context_basic(self, global_config): "service": "test-service", "env": "test-env", "version": "test-version", - } + }, dd_log_record tracer.context_provider.activate( Context( @@ -95,10 +103,17 @@ def test_get_log_correlation_context_basic(self, global_config): "service": "test-service", "env": "test-env", "version": "test-version", - } + }, test_tracer.get_log_correlation_context() + + +@pytest.mark.subprocess() +def test_get_log_correlation_context_opentracer(): + """Ensure expected DDLogRecord generated via get_correlation_log_record with an opentracing Tracer.""" + from ddtrace.opentracer.tracer import Tracer as OT_Tracer + from tests.tracer.test_correlation_log_context import format_trace_id + from tests.utils import override_global_config - def test_get_log_correlation_context_opentracer(self, global_config): - """Ensure expected DDLogRecord generated via get_correlation_log_record with an opentracing Tracer.""" + with override_global_config(dict(service="test-service", env="test-env", version="test-version")): ot_tracer = OT_Tracer(service_name="test-service") with ot_tracer.start_active_span("operation") as scope: dd_span = scope._span._dd_span @@ -109,93 +124,136 @@ def test_get_log_correlation_context_opentracer(self, global_config): "service": "test-service", "env": "test-env", "version": "test-version", - } + }, dd_log_record - def test_get_log_correlation_context_no_active_span(self): - """Ensure empty DDLogRecord generated if no active span.""" - tracer = Tracer() + +@pytest.mark.subprocess() +def test_get_log_correlation_context_no_active_span(): + """Ensure empty DDLogRecord generated if no active span.""" + from ddtrace import Tracer + + tracer = Tracer() + dd_log_record = tracer.get_log_correlation_context() + assert dd_log_record == { + "span_id": "0", + "trace_id": "0", + "service": "ddtrace_subprocess_dir", + "env": "", + "version": "", + }, dd_log_record + + +@pytest.mark.subprocess() +def test_get_log_correlation_context_disabled_tracer(): + """Ensure get_correlation_log_record returns None if tracer is disabled.""" + from ddtrace import Tracer + + tracer = Tracer() + tracer.enabled = False + with tracer.trace("test-span"): dd_log_record = tracer.get_log_correlation_context() - assert dd_log_record == { - "span_id": "0", - "trace_id": "0", - "service": "", - "env": "", - "version": "", - } + assert dd_log_record == { + "span_id": "0", + "trace_id": "0", + "service": "ddtrace_subprocess_dir", + "env": "", + "version": "", + }, dd_log_record - def test_get_log_correlation_context_disabled_tracer(self): - """Ensure get_correlation_log_record returns None if tracer is disabled.""" - tracer = Tracer() - tracer.enabled = False - with tracer.trace("test-span"): - dd_log_record = tracer.get_log_correlation_context() - assert dd_log_record == { - "span_id": "0", - "trace_id": "0", - "service": "", - "env": "", - "version": "", - } - def test_custom_logging_injection(self): - """Ensure custom log injection via get_correlation_log_record returns proper active span information.""" - capture_log = structlog.testing.LogCapture() - structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) - logger = structlog.get_logger() +@pytest.mark.subprocess() +def test_custom_logging_injection_global_config(): + """Ensure custom log injection via get_correlation_log_record returns proper tracer information.""" + from ddtrace import tracer + from ddtrace._trace.provider import _DD_CONTEXTVAR + from ddtrace.contrib.structlog import patch + from tests.tracer.test_correlation_log_context import format_trace_id + from tests.tracer.test_correlation_log_context import tracer_injection + from tests.utils import override_global_config + + patch() + + import structlog + _DD_CONTEXTVAR.set(None) + capture_log = structlog.testing.LogCapture() + structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) + logger = structlog.get_logger() + + with override_global_config(dict(version="global-version", env="global-env", service="global-service")): with tracer.trace("test span") as span: logger.msg("Hello!") - assert len(capture_log.entries) == 1 - assert capture_log.entries[0]["event"] == "Hello!" - dd_log_record = capture_log.entries[0]["dd"] - assert dd_log_record == { - "span_id": str(span.span_id), - "trace_id": format_trace_id(span), - "service": "", - "env": "", - "version": "", - } + assert len(capture_log.entries) == 1 + assert capture_log.entries[0]["event"] == "Hello!" + dd_log_record = capture_log.entries[0]["dd"] + assert dd_log_record == { + "span_id": str(span.span_id), + "trace_id": format_trace_id(span), + "service": "global-service", + "env": "global-env", + "version": "global-version", + }, dd_log_record - def test_custom_logging_injection_global_config(self): - """Ensure custom log injection via get_correlation_log_record returns proper tracer information.""" - _DD_CONTEXTVAR.set(None) - capture_log = structlog.testing.LogCapture() - structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) - logger = structlog.get_logger() - with override_global_config(dict(version="global-version", env="global-env", service="global-service")): - with tracer.trace("test span") as span: - logger.msg("Hello!") +@pytest.mark.subprocess() +def test_custom_logging_injection_no_span(): + """Ensure custom log injection via get_correlation_log_record with no active span returns empty record.""" + from ddtrace._trace.provider import _DD_CONTEXTVAR + from ddtrace.contrib.structlog import patch + from tests.tracer.test_correlation_log_context import tracer_injection + from tests.utils import override_global_config - assert len(capture_log.entries) == 1 - assert capture_log.entries[0]["event"] == "Hello!" - dd_log_record = capture_log.entries[0]["dd"] - assert dd_log_record == { - "span_id": str(span.span_id), - "trace_id": format_trace_id(span), - "service": "global-service", - "env": "global-env", - "version": "global-version", - } + patch() - def test_custom_logging_injection_no_span(self): - """Ensure custom log injection via get_correlation_log_record with no active span returns empty record.""" - _DD_CONTEXTVAR.set(None) - capture_log = structlog.testing.LogCapture() - structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) - logger = structlog.get_logger() + import structlog - with override_global_config(dict(version="global-version", env="global-env", service="global-service")): - logger.msg("No Span!") + _DD_CONTEXTVAR.set(None) + capture_log = structlog.testing.LogCapture() + structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) + logger = structlog.get_logger() - assert len(capture_log.entries) == 1 - assert capture_log.entries[0]["event"] == "No Span!" - dd_log_record = capture_log.entries[0]["dd"] - assert dd_log_record == { - "span_id": "0", - "trace_id": "0", - "service": "global-service", - "env": "global-env", - "version": "global-version", - } + with override_global_config(dict(version="global-version", env="global-env", service="global-service")): + logger.msg("No Span!") + + assert len(capture_log.entries) == 1 + assert capture_log.entries[0]["event"] == "No Span!" + dd_log_record = capture_log.entries[0]["dd"] + assert dd_log_record == { + "span_id": "0", + "trace_id": "0", + "service": "global-service", + "env": "global-env", + "version": "global-version", + }, dd_log_record + + +@pytest.mark.subprocess() +def test_custom_logging_injection(): + """Ensure custom log injection via get_correlation_log_record returns proper active span information.""" + from ddtrace import tracer + from ddtrace.contrib.structlog import patch + from tests.tracer.test_correlation_log_context import format_trace_id + from tests.tracer.test_correlation_log_context import tracer_injection + + patch() + + import structlog + + capture_log = structlog.testing.LogCapture() + structlog.configure(processors=[tracer_injection, capture_log, structlog.processors.JSONRenderer()]) + logger = structlog.get_logger() + + with tracer.trace("test span") as span: + logger.msg("Hello!") + + assert len(capture_log.entries) == 1 + assert capture_log.entries[0]["event"] == "Hello!" + dd_log_record = capture_log.entries[0]["dd"] + assert dd_log_record == { + "span_id": str(span.span_id), + "trace_id": format_trace_id(span), + "service": "ddtrace_subprocess_dir", + "env": "", + "version": "", + }, dd_log_record diff --git a/tests/tracer/test_sampler.py b/tests/tracer/test_sampler.py index bf86dacef0a..ad1496f67ae 100644 --- a/tests/tracer/test_sampler.py +++ b/tests/tracer/test_sampler.py @@ -154,68 +154,72 @@ def test_sample_rate_0_does_not_reset_to_1(self): ), "Setting the sample rate to zero should result in the sample rate being zero" -class RateByServiceSamplerTest(unittest.TestCase): - def test_default_key(self): - assert ( - "service:,env:" == RateByServiceSampler._default_key - ), "default key should correspond to no service and no env" +# RateByServiceSamplerTest Cases +def test_default_key(): + assert ( + "service:,env:" == RateByServiceSampler._default_key + ), "default key should correspond to no service and no env" - def test_key(self): - assert ( - RateByServiceSampler._default_key == RateByServiceSampler._key() - ), "_key() with no arguments returns the default key" - assert "service:mcnulty,env:" == RateByServiceSampler._key( - service="mcnulty" - ), "_key call with service name returns expected result" - assert "service:,env:test" == RateByServiceSampler._key( - env="test" - ), "_key call with env name returns expected result" - assert "service:mcnulty,env:test" == RateByServiceSampler._key( - service="mcnulty", env="test" - ), "_key call with service and env name returns expected result" - assert "service:mcnulty,env:test" == RateByServiceSampler._key( - "mcnulty", "test" - ), "_key call with service and env name as positional args returns expected result" - - @run_in_subprocess(env=dict(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED="true")) - def test_sample_rate_deviation_128bit_trace_id(self): - self._test_sample_rate_deviation() - - @run_in_subprocess(env=dict(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED="false")) - def test_sample_rate_deviation_64bit_trace_id(self): - self._test_sample_rate_deviation() - - def _test_sample_rate_deviation(self): - for sample_rate in [0.1, 0.25, 0.5, 1]: - tracer = DummyTracer() - tracer.configure(sampler=RateByServiceSampler()) - tracer._sampler.set_sample_rate(sample_rate) - iterations = int(1e4 / sample_rate) +def test_key(): + assert ( + RateByServiceSampler._default_key == RateByServiceSampler._key() + ), "_key() with no arguments returns the default key" + assert "service:mcnulty,env:" == RateByServiceSampler._key( + service="mcnulty" + ), "_key call with service name returns expected result" + assert "service:,env:test" == RateByServiceSampler._key( + env="test" + ), "_key call with env name returns expected result" + assert "service:mcnulty,env:test" == RateByServiceSampler._key( + service="mcnulty", env="test" + ), "_key call with service and env name returns expected result" + assert "service:mcnulty,env:test" == RateByServiceSampler._key( + "mcnulty", "test" + ), "_key call with service and env name as positional args returns expected result" + + +@run_in_subprocess(env=dict(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED="true")) +def test_sample_rate_deviation_128bit_trace_id(): + _test_sample_rate_deviation() + + +@run_in_subprocess(env=dict(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED="false", DD_SERVICE="my-svc")) +def test_sample_rate_deviation_64bit_trace_id(): + _test_sample_rate_deviation() + + +def _test_sample_rate_deviation(): + for sample_rate in [0.1, 0.25, 0.5, 1]: + tracer = DummyTracer() + tracer.configure(sampler=RateByServiceSampler()) + tracer._sampler.set_sample_rate(sample_rate) - for i in range(iterations): - span = tracer.trace(str(i)) - span.finish() + iterations = int(1e4 / sample_rate) - samples = tracer.pop() - samples_with_high_priority = 0 - for sample in samples: - sample_priority = sample.context.sampling_priority - samples_with_high_priority += int(bool(sample_priority > 0)) - assert_sampling_decision_tags( - sample, - agent=sample_rate, - trace_tag="-{}".format(SamplingMechanism.AGENT_RATE), - ) - - deviation = abs(samples_with_high_priority - (iterations * sample_rate)) / (iterations * sample_rate) - assert ( - deviation < 0.05 - ), "Actual sample rate should be within 5 percent of set sample " "rate (actual: %f, set: %f)" % ( - deviation, - sample_rate, + for i in range(iterations): + span = tracer.trace(str(i)) + span.finish() + + samples = tracer.pop() + samples_with_high_priority = 0 + for sample in samples: + sample_priority = sample.context.sampling_priority + samples_with_high_priority += int(bool(sample_priority > 0)) + assert_sampling_decision_tags( + sample, + agent=sample_rate, + trace_tag="-{}".format(SamplingMechanism.AGENT_RATE), ) + deviation = abs(samples_with_high_priority - (iterations * sample_rate)) / (iterations * sample_rate) + assert ( + deviation < 0.05 + ), "Actual sample rate should be within 5 percent of set sample " "rate (actual: %f, set: %f)" % ( + deviation, + sample_rate, + ) + @pytest.mark.parametrize( "sample_rate,expectation", @@ -512,10 +516,10 @@ def test_sampling_rule_matches_name(span, rule, span_expected_to_match_rule): for service, pattern, expected_to_match in [ ("my-service", SamplingRule.NO_RULE, True), ("my-service", None, False), - (None, None, True), - (None, "my-service", False), - (None, re.compile(r"my-service"), False), - (None, lambda service: "service" in service, False), + (None, "tests.tracer", True), + ("tests.tracer", "my-service", False), + ("tests.tracer", re.compile(r"my-service"), False), + ("tests.tracer", lambda service: "service" in service, False), ("my-service", "my-service", True), ("my-service", "my_service", False), ("my-service", re.compile(r"^my-"), True), diff --git a/tests/tracer/test_settings.py b/tests/tracer/test_settings.py index 1c80ef4c4dc..c78302712e7 100644 --- a/tests/tracer/test_settings.py +++ b/tests/tracer/test_settings.py @@ -23,7 +23,7 @@ def test_service(self): # If none is provided the default should be ``None`` with self.override_env(dict()): config = Config() - self.assertEqual(config.service, None) + self.assertEqual(config.service, "tests.tracer") with self.override_env(dict(DD_SERVICE="my-service")): config = Config() diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index 42cb77fe0b2..e9869c13b17 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -257,7 +257,7 @@ def test_whitelist_case_insensitive(self, span, integration_config): @pytest.mark.parametrize( "pin,config_val,default,global_service,expected", [ - (Pin(), None, None, None, None), + (Pin(), None, None, None, "tests.tracer"), (Pin(), None, None, "global-svc", "global-svc"), (Pin(), None, "default-svc", None, "default-svc"), # Global service should have higher priority than the integration default. @@ -280,10 +280,13 @@ def test_int_service(int_config, pin, config_val, default, global_service, expec def test_int_service_integration(int_config): pin = Pin() tracer = Tracer() - assert trace_utils.int_service(pin, int_config.myint) is None + assert trace_utils.int_service(pin, int_config.myint) == "tests.tracer" with override_global_config(dict(service="global-svc")): - assert trace_utils.int_service(pin, int_config.myint) is None + # ensure int config picks up overridden changes + int_config = config + + assert trace_utils.int_service(pin, int_config.myint) == "global-svc" with tracer.trace("something", service=trace_utils.int_service(pin, int_config.myint)) as s: assert s.service == "global-svc" diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 29daae4fb09..cd1a2c7cfca 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -61,7 +61,7 @@ def test_tracer_vars(self): span.finish() span = self.trace("a") - span.assert_matches(name="a", service=None, resource="a", span_type=None) + span.assert_matches(name="a", resource="a", span_type=None) span.finish() def test_tracer(self): @@ -400,7 +400,7 @@ def test_start_span_optional(self): def test_start_span_service_default(self): span = self.start_span("") - span.assert_matches(service=None) + span.assert_matches(service="tests.tracer") span.finish() def test_start_span_service_from_parent(self): @@ -465,13 +465,14 @@ def test_start_child_from_context(self): _parent=None, ) + @run_in_subprocess() def test_adding_services(self): - assert self.tracer._services == set() + assert self.tracer._services == set(), self.tracer._services with self.start_span("root", service="one") as root: - assert self.tracer._services == set(["one"]) + assert self.tracer._services == set(["one"]), self.tracer._services with self.start_span("child", service="two", child_of=root): pass - assert self.tracer._services == set(["one", "two"]) + assert self.tracer._services == set(["one", "two"]), self.tracer._services @run_in_subprocess(env_overrides=dict(DD_SERVICE_MAPPING="two:three")) def test_adding_mapped_services(self): From 995a7d59c7256a3092f7bb181d846ebb900da76a Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 19 Nov 2024 16:48:23 -0500 Subject: [PATCH 183/372] chore(otel): collect telemetry for otel configurations (#11351) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/_otel_remapper.py | 117 +++++++++++++++++------------ tests/.suitespec.json | 1 + tests/opentelemetry/test_config.py | 23 +++--- tests/telemetry/test_writer.py | 61 +++++++++++++++ 4 files changed, 145 insertions(+), 57 deletions(-) diff --git a/ddtrace/settings/_otel_remapper.py b/ddtrace/settings/_otel_remapper.py index 1b9dffe9fae..2815dea4cba 100644 --- a/ddtrace/settings/_otel_remapper.py +++ b/ddtrace/settings/_otel_remapper.py @@ -1,16 +1,35 @@ import os import sys +from typing import Callable +from typing import Dict +from typing import List +from typing import Tuple + + +if sys.version_info < (3, 8): + from typing_extensions import Literal + + try: + from typing_extensions import Optional + except ImportError: + # hack to support the Optional type for python3.7 + typing_extensions<4.0 (ex: molton) + from typing import Union + + class Optional: + def __class_getitem__(self, item): + return Union[item, type(None)] + +else: + from typing import Literal + from typing import Optional + from ..constants import ENV_KEY from ..constants import VERSION_KEY from ..internal.logger import get_logger +from ..internal.telemetry import telemetry_writer -if sys.version_info >= (3, 8): - import typing -else: - import typing_extensions as typing # noqa: F401 - log = get_logger(__name__) @@ -21,19 +40,14 @@ } -def _remap_otel_log_level(otel_value: str) -> str: +def _remap_otel_log_level(otel_value: str) -> Optional[str]: """Remaps the otel log level to ddtrace log level""" if otel_value == "debug": return "True" - else: - log.warning( - "ddtrace does not support otel log level '%s'. ddtrace only supports enabling debug logs.", - otel_value, - ) - return "False" + return None -def _remap_otel_propagators(otel_value: str) -> str: +def _remap_otel_propagators(otel_value: str) -> Optional[str]: """Remaps the otel propagators to ddtrace propagators""" accepted_styles = [] for style in otel_value.split(","): @@ -43,10 +57,10 @@ def _remap_otel_propagators(otel_value: str) -> str: accepted_styles.append(style) else: log.warning("Following style not supported by ddtrace: %s.", style) - return ",".join(accepted_styles) + return ",".join(accepted_styles) or None -def _remap_traces_sampler(otel_value: str) -> str: +def _remap_traces_sampler(otel_value: str) -> Optional[str]: """Remaps the otel trace sampler to ddtrace trace sampler""" if otel_value in ["always_on", "always_off", "traceidratio"]: log.warning( @@ -61,47 +75,36 @@ def _remap_traces_sampler(otel_value: str) -> str: return "0.0" elif otel_value == "parentbased_traceidratio": return os.environ.get("OTEL_TRACES_SAMPLER_ARG", "1") - else: - log.warning("Unknown sampling configuration: %s.", otel_value) - return otel_value + return None -def _remap_traces_exporter(otel_value: str) -> str: +def _remap_traces_exporter(otel_value: str) -> Optional[str]: """Remaps the otel trace exporter to ddtrace trace enabled""" if otel_value == "none": return "False" - log.warning( - "A trace exporter value '%s' is set, but not supported. Traces will be exported to Datadog.", otel_value - ) - return "" + return None -def _remap_metrics_exporter(otel_value: str) -> str: +def _remap_metrics_exporter(otel_value: str) -> Optional[str]: """Remaps the otel metrics exporter to ddtrace metrics exporter""" if otel_value == "none": return "False" - log.warning( - "Metrics exporter value is set to unrecognized value: %s.", - otel_value, - ) - return "" + return None -def _validate_logs_exporter(otel_value: str) -> typing.Literal[""]: +def _validate_logs_exporter(otel_value: str) -> Literal["", None]: """Logs warning when OTEL Logs exporter is configured. DDTRACE does not support this configuration.""" - if otel_value != "none": - log.warning( - "Unsupported OTEL logs exporter value detected: %s. Only the 'none' value is supported.", otel_value - ) - return "" + if otel_value == "none": + return "" + return None -def _remap_otel_tags(otel_value: str) -> str: +def _remap_otel_tags(otel_value: str) -> Optional[str]: """Remaps the otel tags to ddtrace tags""" - dd_tags: typing.List[str] = [] + dd_tags: List[str] = [] try: - otel_user_tag_dict: typing.Dict[str, str] = dict() + otel_user_tag_dict: Dict[str, str] = dict() for tag in otel_value.split(","): key, value = tag.split("=") otel_user_tag_dict[key] = value @@ -113,7 +116,7 @@ def _remap_otel_tags(otel_value: str) -> str: else: dd_tags.append(f"{key}:{value}") except Exception: - log.warning("DDTRACE failed to read OTEL_RESOURCE_ATTRIBUTES. This value is misformatted: %s", otel_value) + return None if len(dd_tags) > 10: dd_tags, remaining_tags = dd_tags[:10], dd_tags[10:] @@ -126,23 +129,21 @@ def _remap_otel_tags(otel_value: str) -> str: return ",".join(dd_tags) -def _remap_otel_sdk_config(otel_value: str) -> str: +def _remap_otel_sdk_config(otel_value: str) -> Optional[str]: """Remaps the otel sdk config to ddtrace sdk config""" if otel_value == "false": return "True" elif otel_value == "true": return "False" - else: - log.warning("OTEL_SDK_DISABLED='%s' is not supported", otel_value) - return otel_value + return None -def _remap_default(otel_value: str) -> str: +def _remap_default(otel_value: str) -> Optional[str]: """Remaps the otel default value to ddtrace default value""" return otel_value -ENV_VAR_MAPPINGS = { +ENV_VAR_MAPPINGS: Dict[str, Tuple[str, Callable[[str], Optional[str]]]] = { "OTEL_SERVICE_NAME": ("DD_SERVICE", _remap_default), "OTEL_LOG_LEVEL": ("DD_TRACE_DEBUG", _remap_otel_log_level), "OTEL_PROPAGATORS": ("DD_TRACE_PROPAGATION_STYLE", _remap_otel_propagators), @@ -164,6 +165,11 @@ def otel_remapping(): for otel_env, otel_value in user_envs.items(): if otel_env not in ENV_VAR_MAPPINGS: + if otel_env.startswith("OTEL_") and otel_env != "OTEL_PYTHON_CONTEXT": + log.warning("OpenTelemetry configuration %s is not supported by Datadog.", otel_env) + telemetry_writer.add_count_metric( + "tracer", "otel.env.unsupported", 1, (("config_opentelemetry", otel_env.lower()),) + ) continue dd_env, otel_config_validator = ENV_VAR_MAPPINGS[otel_env] @@ -174,14 +180,33 @@ def otel_remapping(): otel_env, otel_value, ) + telemetry_writer.add_count_metric( + "tracer", + "otel.env.hiding", + 1, + (("config_opentelemetry", otel_env.lower()), ("config_datadog", dd_env.lower())), + ) continue if otel_env not in ("OTEL_RESOURCE_ATTRIBUTES", "OTEL_SERVICE_NAME"): # Resource attributes and service name are case-insensitive otel_value = otel_value.lower() + telemetry_writer.add_configuration(otel_env, otel_value, "env_var") mapped_value = otel_config_validator(otel_value) - if mapped_value: + if mapped_value is None: + log.warning( + "Setting %s to %s is not supported by ddtrace, this configuration will be ignored.", + otel_env, + otel_value, + ) + telemetry_writer.add_count_metric( + "tracer", + "otel.env.invalid", + 1, + (("config_opentelemetry", otel_env.lower()), ("config_datadog", dd_env.lower())), + ) + elif mapped_value != "": os.environ[dd_env] = mapped_value log.debug( "OpenTelemetry configuration %s has been remapped to ddtrace configuration %s=%s", diff --git a/tests/.suitespec.json b/tests/.suitespec.json index 1e85eff0ffa..a1200ec4704 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -300,6 +300,7 @@ "ddtrace/contrib/internal/sqlalchemy/*" ], "opentelemetry": [ + "ddtrace/settings/_otel_remapper.py", "ddtrace/opentelemetry/*", "ddtrace/internal/opentelemetry/*" ], diff --git a/tests/opentelemetry/test_config.py b/tests/opentelemetry/test_config.py index fc25d973f9f..39a43128e9e 100644 --- a/tests/opentelemetry/test_config.py +++ b/tests/opentelemetry/test_config.py @@ -22,7 +22,7 @@ "OTEL_SDK_DISABLED": "True", "DD_TRACE_OTEL_ENABLED": "True", }, - err=b"Unsupported OTEL logs exporter value detected: warning. Only the 'none' value is supported.\n", + err=b"Setting OTEL_LOGS_EXPORTER to warning is not supported by ddtrace, this configuration will be ignored.\n", ) def test_dd_otel_mixed_env_configuration(): from ddtrace import config @@ -54,8 +54,8 @@ def test_dd_otel_mixed_env_configuration(): "OTEL_SDK_DISABLED": "False", }, err=b"Following style not supported by ddtrace: jaegar.\n" - b"A trace exporter value 'otlp' is set, but not supported. Traces will be exported to Datadog.\n" - b"Unsupported OTEL logs exporter value detected: warning. Only the 'none' value is supported.\n", + b"Setting OTEL_TRACES_EXPORTER to otlp is not supported by ddtrace, " + b"this configuration will be ignored.\n", ) def test_dd_otel_missing_dd_env_configuration(): from ddtrace import config @@ -93,7 +93,7 @@ def test_otel_log_level_configuration_debug(): @pytest.mark.subprocess( env={"OTEL_LOG_LEVEL": "trace"}, - err=b"ddtrace does not support otel log level 'trace'. ddtrace only supports enabling debug logs.\n", + err=b"Setting OTEL_LOG_LEVEL to trace is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_log_level_configuration_info(): from ddtrace import config @@ -103,7 +103,7 @@ def test_otel_log_level_configuration_info(): @pytest.mark.subprocess( env={"OTEL_LOG_LEVEL": "warning"}, - err=b"ddtrace does not support otel log level 'warning'. ddtrace only supports enabling debug logs.\n", + err=b"Setting OTEL_LOG_LEVEL to warning is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_log_level_configuration_unsupported(): from ddtrace import config @@ -162,7 +162,8 @@ def test_otel_traces_sampler_configuration_alwaysoff(): "OTEL_TRACES_SAMPLER": "traceidratio", "OTEL_TRACES_SAMPLER_ARG": "0.5", }, - err=b"Trace sampler set from traceidratio to parentbased_traceidratio; only parent based sampling is supported.\n", + err=b"Trace sampler set from traceidratio to parentbased_traceidratio; only parent based sampling is supported.\n" + b"OpenTelemetry configuration OTEL_TRACES_SAMPLER_ARG is not supported by Datadog.\n", ) def test_otel_traces_sampler_configuration_traceidratio(): from ddtrace import config @@ -179,7 +180,7 @@ def test_otel_traces_exporter_configuration(): @pytest.mark.subprocess( env={"OTEL_TRACES_EXPORTER": "true"}, - err=b"A trace exporter value 'true' is set, but not supported. Traces will be exported to Datadog.\n", + err=b"Setting OTEL_TRACES_EXPORTER to true is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_traces_exporter_configuration_unsupported_exporter(): from ddtrace import config @@ -196,7 +197,7 @@ def test_otel_metrics_exporter_configuration(): @pytest.mark.subprocess( env={"OTEL_METRICS_EXPORTER": "true"}, - err=b"Metrics exporter value is set to unrecognized value: true.\n", + err=b"Setting OTEL_METRICS_EXPORTER to true is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_metrics_exporter_configuration_unsupported_exporter(): from ddtrace import config @@ -206,7 +207,7 @@ def test_otel_metrics_exporter_configuration_unsupported_exporter(): @pytest.mark.subprocess( env={"otel_LOGS_EXPORTER": "console"}, - err=b"Unsupported OTEL logs exporter value detected: console. Only the 'none' value is supported.\n", + err=b"Setting OTEL_LOGS_EXPORTER to console is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_logs_exporter_configuration_unsupported(): from ddtrace import config # noqa: F401 @@ -233,8 +234,8 @@ def test_otel_resource_attributes_unified_tags(): @pytest.mark.subprocess( env={"OTEL_RESOURCE_ATTRIBUTES": "deployment.environment:prod,service.name:bleh,service.version:1.0"}, - err=b"DDTRACE failed to read OTEL_RESOURCE_ATTRIBUTES. This value is misformatted: " - b"deployment.environment:prod,service.name:bleh,service.version:1.0\n", + err=b"Setting OTEL_RESOURCE_ATTRIBUTES to deployment.environment:prod,service.name:bleh,service.version:1.0" + b" is not supported by ddtrace, this configuration will be ignored.\n", ) def test_otel_resource_attributes_misconfigured_tags(): from ddtrace import config # noqa: F401 diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 8bb6f9ced4a..365bfb0cdc0 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -813,3 +813,64 @@ def test_telemetry_writer_is_using_agent_by_default_if_api_key_is_not_available( assert telemetry_writer._client._endpoint == "telemetry/proxy/api/v2/apmtelemetry" assert telemetry_writer._client._telemetry_url in ("http://localhost:9126", "http://testagent:9126") assert "dd-api-key" not in telemetry_writer._client._headers + + +def test_otel_config_telemetry(test_agent_session, run_python_code_in_subprocess, tmpdir): + """ + asserts that telemetry data is submitted for OpenTelemetry configurations + """ + + env = os.environ.copy() + env["DD_SERVICE"] = "dd_service" + env["OTEL_SERVICE_NAME"] = "otel_service" + env["OTEL_LOG_LEVEL"] = "DEBUG" + env["OTEL_PROPAGATORS"] = "tracecontext" + env["OTEL_TRACES_SAMPLER"] = "always_on" + env["OTEL_TRACES_EXPORTER"] = "none" + env["OTEL_LOGS_EXPORTER"] = "otlp" + env["OTEL_RESOURCE_ATTRIBUTES"] = "team=apm,component=web" + env["OTEL_SDK_DISABLED"] = "true" + env["OTEL_UNSUPPORTED_CONFIG"] = "value" + env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true" + + _, stderr, status, _ = run_python_code_in_subprocess("import ddtrace", env=env) + assert status == 0, stderr + + configurations = {c["name"]: c for c in test_agent_session.get_configurations()} + + assert configurations["DD_SERVICE"] == {"name": "DD_SERVICE", "origin": "env_var", "value": "dd_service"} + assert configurations["OTEL_LOG_LEVEL"] == {"name": "OTEL_LOG_LEVEL", "origin": "env_var", "value": "debug"} + assert configurations["OTEL_PROPAGATORS"] == { + "name": "OTEL_PROPAGATORS", + "origin": "env_var", + "value": "tracecontext", + } + assert configurations["OTEL_TRACES_SAMPLER"] == { + "name": "OTEL_TRACES_SAMPLER", + "origin": "env_var", + "value": "always_on", + } + assert configurations["OTEL_TRACES_EXPORTER"] == { + "name": "OTEL_TRACES_EXPORTER", + "origin": "env_var", + "value": "none", + } + assert configurations["OTEL_LOGS_EXPORTER"] == {"name": "OTEL_LOGS_EXPORTER", "origin": "env_var", "value": "otlp"} + assert configurations["OTEL_RESOURCE_ATTRIBUTES"] == { + "name": "OTEL_RESOURCE_ATTRIBUTES", + "origin": "env_var", + "value": "team=apm,component=web", + } + assert configurations["OTEL_SDK_DISABLED"] == {"name": "OTEL_SDK_DISABLED", "origin": "env_var", "value": "true"} + + env_hiding_metrics = test_agent_session.get_metrics("otel.env.hiding") + tags = [m["tags"] for m in env_hiding_metrics] + assert tags == [["config_opentelemetry:otel_service_name", "config_datadog:dd_service"]] + + env_unsupported_metrics = test_agent_session.get_metrics("otel.env.unsupported") + tags = [m["tags"] for m in env_unsupported_metrics] + assert tags == [["config_opentelemetry:otel_unsupported_config"]] + + env_invalid_metrics = test_agent_session.get_metrics("otel.env.invalid") + tags = [m["tags"] for m in env_invalid_metrics] + assert tags == [["config_opentelemetry:otel_logs_exporter", "config_datadog:"]] From eb5a8d7d767152a7bef74ae2983be2acbd503bd8 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 19 Nov 2024 16:48:53 -0500 Subject: [PATCH 184/372] fix(grpc.aio): resolve distributed tracing concurrency bug (#11415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the tracer accessed the grpc metadata (which contained distributing tracing headers) directly from the Server’s [HandlerCallDetails](https://grpc.github.io/grpc/python/grpc.html#grpc.HandlerCallDetails) ([here](https://github.com/DataDog/dd-trace-py/blob/v2.14.0/ddtrace/contrib/internal/grpc/aio_server_interceptor.py#L217)). This was not a safe operation, since the server’s handlerdetails is modified/set by every request. When multiple requests were handled concurrently the grpcaio integration would overwrite distributing tracing headers and clobber multiple unrelated trace chunks into one trace. This PR ensures distributed tracing headers are extracted from the call handler details that's unique to the request: [ServicerContext](https://grpc.github.io/grpc/python/grpc.html#grpc.ServicerContext). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/grpc/aio_server_interceptor.py | 80 ++++++++++++++----- ...text-to-get-metadata-84c144a247267893.yaml | 4 + 2 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/grpc-use-ServicerContext-context-to-get-metadata-84c144a247267893.yaml diff --git a/ddtrace/contrib/internal/grpc/aio_server_interceptor.py b/ddtrace/contrib/internal/grpc/aio_server_interceptor.py index 69a24b8b4bb..2361e3c3be9 100644 --- a/ddtrace/contrib/internal/grpc/aio_server_interceptor.py +++ b/ddtrace/contrib/internal/grpc/aio_server_interceptor.py @@ -172,18 +172,18 @@ def _wrap_unary_response( span.finish() -def _create_span(pin, handler_call_details, method_kind): - # type: (Pin, grpc.HandlerCallDetails, str) -> Span +def _create_span(pin, method, invocation_metadata, method_kind): + # type: (Pin, str, grpc.HandlerCallDetails, str) -> Span tracer = pin.tracer - headers = dict(handler_call_details.invocation_metadata) - - trace_utils.activate_distributed_headers(tracer, int_config=config.grpc_aio_server, request_headers=headers) + trace_utils.activate_distributed_headers( + tracer, int_config=config.grpc_aio_server, request_headers=dict(invocation_metadata) + ) span = tracer.trace( schematize_url_operation("grpc", protocol="grpc", direction=SpanDirection.INBOUND), span_type=SpanTypes.GRPC, service=trace_utils.int_service(pin, config.grpc_aio_server), - resource=handler_call_details.method, + resource=method, ) span.set_tag_str(COMPONENT, config.grpc_aio_server.integration_name) @@ -193,7 +193,7 @@ def _create_span(pin, handler_call_details, method_kind): span.set_tag(SPAN_MEASURED_KEY) - set_grpc_method_meta(span, handler_call_details.method, method_kind) + set_grpc_method_meta(span, method, method_kind) span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER) sample_rate = config.grpc_aio_server.get_analytics_sample_rate() @@ -211,22 +211,37 @@ def __init__(self, pin, handler_call_details, wrapped): # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None super(_TracedCoroRpcMethodHandler, self).__init__(wrapped) self._pin = pin - self._handler_call_details = handler_call_details + self.method = handler_call_details.method async def unary_unary(self, request: RequestType, context: aio.ServicerContext) -> ResponseType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_UNARY) + span = _create_span(self._pin, self.method, context.invocation_metadata(), constants.GRPC_METHOD_KIND_UNARY) return await _wrap_aio_unary_response(self.__wrapped__.unary_unary, request, context, span) async def unary_stream(self, request: RequestType, context: aio.ServicerContext) -> ResponseType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_SERVER_STREAMING, + ) return await _wrap_aio_unary_response(self.__wrapped__.unary_stream, request, context, span) async def stream_unary(self, request_iterator: RequestIterableType, context: aio.ServicerContext) -> ResponseType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_CLIENT_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_CLIENT_STREAMING, + ) return await _wrap_aio_unary_response(self.__wrapped__.stream_unary, request_iterator, context, span) async def stream_stream(self, request_iterator: RequestIterableType, context: aio.ServicerContext) -> ResponseType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_BIDI_STREAMING, + ) return await _wrap_aio_unary_response(self.__wrapped__.stream_stream, request_iterator, context, span) @@ -235,17 +250,27 @@ def __init__(self, pin, handler_call_details, wrapped): # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None super(_TracedAsyncGenRpcMethodHandler, self).__init__(wrapped) self._pin = pin - self._handler_call_details = handler_call_details + self.method = handler_call_details.method async def unary_stream(self, request: RequestType, context: aio.ServicerContext) -> ResponseIterableType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_SERVER_STREAMING, + ) async for response in _wrap_aio_stream_response(self.__wrapped__.unary_stream, request, context, span): yield response async def stream_stream( self, request_iterator: RequestIterableType, context: aio.ServicerContext ) -> ResponseIterableType: - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_BIDI_STREAMING, + ) async for response in _wrap_aio_stream_response( self.__wrapped__.stream_stream, request_iterator, context, span ): @@ -257,27 +282,42 @@ def __init__(self, pin, handler_call_details, wrapped): # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None super(_TracedRpcMethodHandler, self).__init__(wrapped) self._pin = pin - self._handler_call_details = handler_call_details + self.method = handler_call_details.method def unary_unary(self, request, context): # type: (Any, grpc.ServicerContext) -> Any - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_UNARY) + span = _create_span(self._pin, self.method, context.invocation_metadata(), constants.GRPC_METHOD_KIND_UNARY) return _wrap_unary_response(self.__wrapped__.unary_unary, request, context, span) def unary_stream(self, request, context): # type: (Any, grpc.ServicerContext) -> Iterable[Any] - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_SERVER_STREAMING, + ) for response in _wrap_stream_response(self.__wrapped__.unary_stream, request, context, span): yield response def stream_unary(self, request_iterator, context): # type: (Iterable[Any], grpc.ServicerContext) -> Any - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_CLIENT_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_CLIENT_STREAMING, + ) return _wrap_unary_response(self.__wrapped__.stream_unary, request_iterator, context, span) def stream_stream(self, request_iterator, context): # type: (Iterable[Any], grpc.ServicerContext) -> Iterable[Any] - span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + span = _create_span( + self._pin, + self.method, + context.invocation_metadata(), + constants.GRPC_METHOD_KIND_BIDI_STREAMING, + ) for response in _wrap_stream_response(self.__wrapped__.stream_stream, request_iterator, context, span): yield response diff --git a/releasenotes/notes/grpc-use-ServicerContext-context-to-get-metadata-84c144a247267893.yaml b/releasenotes/notes/grpc-use-ServicerContext-context-to-get-metadata-84c144a247267893.yaml new file mode 100644 index 00000000000..caad089eb5f --- /dev/null +++ b/releasenotes/notes/grpc-use-ServicerContext-context-to-get-metadata-84c144a247267893.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + grpcaio: Resolves a concurrency bug where distributed tracing headers were overwritten resulting in spans being assigned to the wrong trace. From 45e4dc6b7761a37b205e8212e60131c9d880e32c Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:35:07 -0500 Subject: [PATCH 185/372] chore(ci): make the GitLab `testrunner` definition interruptible (#11408) Making the base definition for all GitLab jobs (`testrunner`) interruptible, so that older pipelines/jobs can be auto-cancelled when newer commits are pushed to a branch. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3e44cc8e75..e5ee6e5a2b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,9 @@ variables: REPO_LANG: python # "python" is used everywhere rather than "py" # CI_DEBUG_SERVICES: "true" +default: + interruptible: true + include: - remote: https://gitlab-templates.ddbuild.io/libdatadog/include/one-pipeline.yml - local: ".gitlab/services.yml" # Include early so others can use the definitions From bad0d272e2b1bc5dd192e915bf67de0d2681ae21 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:21:16 -0500 Subject: [PATCH 186/372] chore: automate updating `version_scheme` in pyproject.toml (#11427) This PR automates two manual steps in the release process: - Creating the new release branch for minor versions (ex - `2.17` branch after 2.17.0rc1 is built and published, so contributors can backport) - Automating [these manual PRs](https://github.com/DataDog/dd-trace-py/pull/11228) to update the `version_scheme` in the `pyproject.toml` file from `release-branch-semver` to `guess-next-dev`. This is a required step in the release process after the first release candidate is triggered to ensure system tests work properly for backports. See sample PR that was generated via testing here: https://github.com/DataDog/dd-trace-py/pull/11426 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/create_new_release_branch.py | 207 +++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 scripts/create_new_release_branch.py diff --git a/scripts/create_new_release_branch.py b/scripts/create_new_release_branch.py new file mode 100644 index 00000000000..692b1263c06 --- /dev/null +++ b/scripts/create_new_release_branch.py @@ -0,0 +1,207 @@ +import os +import re +import subprocess + +from github import Github + + +""" +This script automates two steps in the release process: + +- Creating the new release branch for minor versions (ex - 2.17 branch after + 2.17.0rc1 is built and published, so contributors can backport) +- Update the version_scheme in the pyproject.toml file from release-branch-semver to + guess-next-dev. This is a required step in the release process after the first release + candidate is triggered to ensure system tests work properly for backports. + +Setup: +1. Create a Personal access token (classic), not a fine grained one, on Github: +https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#creating-a-personal-access-token-classic +2. Give the Github token repo, user, audit_log, project permissions. + On the next page authorize your token for Datadog SSO. +3. Add `export GH_TOKEN=` to your `.zhrc` file. + + +4. Create an activate a virtual environment, and install required packages : +`python -m venv venv && source venv/bin/activate && pip install pygithub` + + +Usage: +The script should be run from the `scripts` directory. + +Required: + BASE - The major and minor version you are creating the new release branch for (e.g. BASE=2.17) + +Optional: + DRY_RUN - When set to "1", this script does not write/push anything to github. + It will do all steps locally so you can see what would have been pushed. + +General Process: +1. Run the release.py script for the first release candidate (ex - 2.17.0rc1) +2. Once draft release notes have been approved, trigger the build and publish for the first release candidate. +3. Confirm that the release candidate tag has been created (ex - v2.17.0rc1) +4. Run this script: + BASE=2.17 python3 create_new_release_branch.py +5. You should see that a new release branch should be created (ex - 2.17) along with a PR with the changes in the + pyproject.toml file. Ensure this gets approved and merged before the first backport for that release is created. +""" + +DEFAULT_BRANCH = "main" +BASE = os.getenv("BASE", "") +DRY_RUN = os.getenv("DRY_RUN", "0") == "1" +PYPROJECT_FILENAME = "../pyproject.toml" + + +def create_release_branch(): + rc1_tag = f"v{BASE}.0rc1" + + try: + subprocess.check_call(f"git checkout {rc1_tag}", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError: + print(f"\u274C Failed to checkout {rc1_tag} tag") + raise + + create_new_branch(BASE) + + if DRY_RUN: + print( + f"DRY RUN: Would push {BASE} branch from {rc1_tag} tag to origin." + f"You can check out what would have been pushed in your local {BASE}" + ) + else: + try: + subprocess.check_call(f"git push origin {BASE}", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError as e: + print(f"\u274C Encountered error when trying to push {BASE} branch") + raise e + + print(f"\u2705 Checked out and pushed {BASE} branch created from {rc1_tag} commit") + + +def update_version_scheme(): + with open(PYPROJECT_FILENAME, "r") as f: + content = f.read() + + # Replace the version scheme value + content = re.sub( + r'version_scheme\s*=\s*".*?"', + 'version_scheme = "guess-next-dev"', + content, + ) + with open(PYPROJECT_FILENAME, "w") as f: + f.write(content) + print("\u2705 version_scheme updated to 'guess-next-dev'.") + + +def create_pull_request(): + # Get GH token + gh_token = os.getenv("GH_TOKEN") + if not gh_token: + raise ValueError( + "\u274C We need a Github token to create the PR. Please set GH_TOKEN in your environment variables" + ) + dd_repo = Github(gh_token).get_repo(full_name_or_id="DataDog/dd-trace-py") + + # Create branch + pr_branch_name = f"script/guess-next-dev-{BASE}" + + create_new_branch(pr_branch_name) + + # Update pyproject.toml + update_version_scheme() + try: + subprocess.check_output(f"git add {PYPROJECT_FILENAME}", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError: + try: + subprocess.check_output("git add pyproject.toml", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError: + raise ValueError( + f"\u274C Couldn't find the {PYPROJECT_FILENAME} file when trying to modify and create PR for it." + "You may need to run this script from the root of the repository." + ) + print(f"Committing changes to {PYPROJECT_FILENAME} on branch {pr_branch_name}") + pr_title = f"chore: use guess-next-dev instead of release-branch-semver [{BASE}]" + pr_body = ( + f"This PR updates the `version_schema` in the `{PYPROJECT_FILENAME}` file for the {BASE} release branch " + "from `release-branch-semver` to `guess-next-dev`. This is to ensure that system tests work as intended " + "with backports to this release branch." + "\n\n" + f"IMPORTANT: This PR needs to be merged before the first backport is created for {BASE}." + "Otherwise, system tests will not work as expected." + ) + try: + subprocess.check_output( + f"git commit -m 'Update version_schema for the {BASE} release branch via script'", shell=True, cwd=os.pardir + ) + except subprocess.CalledProcessError: + print(f"\u274C Failed to commit changes to {pr_branch_name}") + raise + if DRY_RUN: + print(f"DRY RUN: Would create PR with target branch set to {pr_branch_name}") + else: + try: + subprocess.check_output(f"git push origin {pr_branch_name}", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError: + print(f"\u274C Failed to push committed changes to {pr_branch_name}") + raise + + try: + pr = dd_repo.create_pull(title=pr_title, body=pr_body, base=BASE, head=pr_branch_name, draft=False) + pr.add_to_labels("changelog/no-changelog") + print("\u2705 Created PR") + except Exception as e: + print(f"\u274C Failed to create PR from {pr_branch_name} into {BASE} due to: {e}") + raise + print("\u2705 Done") + + +def create_new_branch(branch_name: str): + try: + subprocess.check_call(f"git checkout -b {branch_name}", shell=True, cwd=os.pardir) + except subprocess.CalledProcessError as e: + # Capture the error message + error_message = e.stderr.decode("utf-8") if e.stderr else str(e) + if f"Command 'git checkout -b {branch_name}' returned non-zero exit status 128." in error_message: + print(f"\u2705 Branch '{branch_name}' already exists. Skipping branch creation...") + subprocess.check_call(f"git checkout {branch_name}", shell=True, cwd=os.pardir) + else: + print(f"\u274C Encountered error when trying to create branch {branch_name}") + raise e + + +if __name__ == "__main__": + subprocess.check_output( + "git stash", + shell=True, + cwd=os.pardir, + ) + start_branch = ( + subprocess.check_output( + "git rev-parse --abbrev-ref HEAD", + shell=True, + cwd=os.pardir, + ) + .decode() + .strip() + ) + + if BASE == "": + raise ValueError("Need to specify the base ref with env var e.g. BASE=2.10") + if ".x" in BASE: + raise ValueError("Base ref must be a fully qualified semantic version.") + + create_release_branch() + create_pull_request() + + # switch back to original git branch + subprocess.check_output( + "git checkout {start_branch}".format(start_branch=start_branch), + shell=True, + cwd=os.pardir, + ) + print( + ( + "\nYou've been switched back to your original branch, if you had uncommitted changes before" + "running this command, run `git stash pop` to get them back." + ) + ) From a2ca3936fb69e38d3007195ab7041e9a47405f32 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Wed, 20 Nov 2024 09:09:31 +0100 Subject: [PATCH 187/372] ci: use fixtures to reset dd-trace-debug and caplog level (#11437) --- tests/tracer/test_endpoint_config.py | 102 +++++++++++++++------------ 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/tests/tracer/test_endpoint_config.py b/tests/tracer/test_endpoint_config.py index d1d2e484002..df35d43e243 100644 --- a/tests/tracer/test_endpoint_config.py +++ b/tests/tracer/test_endpoint_config.py @@ -2,6 +2,7 @@ from http.client import HTTPResponse from io import BytesIO +import logging from unittest import mock from ddtrace.internal.http import HTTPConnection @@ -84,87 +85,98 @@ def mock_pass(self, *args, **kwargs): def test_unset_config_endpoint(caplog): - caplog.set_level(10) - with override_env({"DD_TRACE_DEBUG": "true"}): + with caplog.at_level(logging.DEBUG, logger="ddtrace"): assert fetch_config_from_endpoint() == {} - assert "Configuration endpoint not set. Skipping fetching configuration." in caplog.text + if caplog.text: + assert "Configuration endpoint not set. Skipping fetching configuration." in caplog.text def test_set_config_endpoint_enabled(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object( - HTTPConnection, "connect", new=mock_pass - ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object( + HTTPConnection, "send", new=mock_pass + ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_enabled ): assert fetch_config_from_endpoint() == {"dd_iast_enabled": True} - assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text - assert "Failed to fetch configuration from endpoint" not in caplog.text + if caplog.text: + assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text + assert "Failed to fetch configuration from endpoint" not in caplog.text def test_set_config_endpoint_500(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( - HTTPConnection, "connect", new=mock_pass - ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object( + HTTPConnection, "send", new=mock_pass + ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_500 ): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint" in caplog.text - assert "RetryError: Response(status=500" in caplog.text + if caplog.text: + assert "Failed to fetch configuration from endpoint" in caplog.text + assert "RetryError: Response(status=500" in caplog.text def test_set_config_endpoint_403(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( - HTTPConnection, "connect", new=mock_pass - ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object( + HTTPConnection, "send", new=mock_pass + ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_403 ): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint" in caplog.text - assert "RetryError: Response(status=403" in caplog.text + if caplog.text: + assert "Failed to fetch configuration from endpoint" in caplog.text + assert "RetryError: Response(status=403" in caplog.text def test_set_config_endpoint_malformed(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}), mock.patch.object( - HTTPConnection, "connect", new=mock_pass - ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object( + HTTPConnection, "send", new=mock_pass + ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_malformed ): assert fetch_config_from_endpoint() == {} - assert "Expecting property name enclosed in double quotes" in caplog.text + if caplog.text: + assert "Unable to parse Datadog Agent JSON" in caplog.text def test_set_config_endpoint_connection_refused(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}): + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80"}): assert fetch_config_from_endpoint() == {} - assert "Failed to fetch configuration from endpoint" in caplog.text - assert any( - message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") - ), "None of the expected connection error log messages were found" + + if caplog.text: + assert "Failed to fetch configuration from endpoint" in caplog.text + assert any( + message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") + ), "None of the expected connection error log messages were found" def test_set_config_endpoint_timeout_error(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch( - "ddtrace.internal.utils.http.get_connection", side_effect=TimeoutError - ): + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch("ddtrace.internal.utils.http.get_connection", side_effect=TimeoutError): assert fetch_config_from_endpoint() == {} - assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text - assert "Failed to fetch configuration from endpoint" in caplog.text - assert any( - message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") - ), "None of the expected connection error log messages were found" + + if caplog.text: + assert "Configuration endpoint not set. Skipping fetching configuration." not in caplog.text + assert "Failed to fetch configuration from endpoint" in caplog.text + assert any( + message in caplog.text for message in ("Connection refused", "Address family not supported by protocol") + ), "None of the expected connection error log messages were found" def test_set_config_endpoint_retries(caplog): - caplog.set_level(10) - with override_env({"_DD_CONFIG_ENDPOINT": "http://localhost:80", "DD_TRACE_DEBUG": "true"}), mock.patch.object( - HTTPConnection, "connect", new=mock_pass - ), mock.patch.object(HTTPConnection, "send", new=mock_pass), mock.patch.object( + with caplog.at_level(logging.DEBUG, logger="ddtrace"), override_env( + {"_DD_CONFIG_ENDPOINT": "http://localhost:80"} + ), mock.patch.object(HTTPConnection, "connect", new=mock_pass), mock.patch.object( + HTTPConnection, "send", new=mock_pass + ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_enabled_after_4_retries ), mock.patch( "ddtrace.settings.endpoint_config._get_retries", return_value=5 From d860dc8907bc70569b89adfa3b148aa4d74f5044 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 20 Nov 2024 10:31:49 +0100 Subject: [PATCH 188/372] chore(ci): add more re tests (#11200) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../bm/iast_fixtures/str_methods_py3.py | 15 ++++++++ tests/appsec/iast/aspects/test_re_aspects.py | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/benchmarks/bm/iast_fixtures/str_methods_py3.py b/benchmarks/bm/iast_fixtures/str_methods_py3.py index 9698afed88c..84c14802e4d 100644 --- a/benchmarks/bm/iast_fixtures/str_methods_py3.py +++ b/benchmarks/bm/iast_fixtures/str_methods_py3.py @@ -1,4 +1,5 @@ # Python 3 only functions (syntax errors on Python 2) +import re from typing import TYPE_CHECKING # noqa:F401 @@ -9,6 +10,12 @@ from typing import Tuple # noqa:F401 +COMPILED_RE = re.compile( + r"(? str return f"{a:05d}" @@ -131,3 +138,11 @@ def resolve(self, path): # type: (str) -> Optional[ResolverMatch, None] tried.append([pattern]) raise Resolver404({"tried": tried, "path": new_path}) raise Resolver404({"path": path}) + + +def do_match_group(text): + # + # TODO(avara1986): This kind of assignation doesn't work with AST patching + # my_re_match_function = COMPILED_RE.match + result = COMPILED_RE.match(text, 0) + return result.group() diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index 83f0f470854..b5069948a89 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -22,6 +22,10 @@ from ddtrace.appsec._iast._taint_tracking.aspects import re_sub_aspect from ddtrace.appsec._iast._taint_tracking.aspects import re_subn_aspect from ddtrace.appsec._iast._taint_tracking.aspects import split_aspect +from tests.appsec.iast.aspects.conftest import _iast_patched_module + + +mod = _iast_patched_module("benchmarks.bm.iast_fixtures.str_methods_py3") def test_re_findall_aspect_tainted_string(): @@ -686,3 +690,33 @@ def test_re_match_getitem_aspect_not_tainted_string_re_object(): assert not is_pyobject_tainted(isaac_newton) assert not is_pyobject_tainted(isaac) assert not is_pyobject_tainted(newton) + + +@pytest.mark.parametrize( + "input_str, expected_result, tainted", + [ + ("print('Hello, world!')", "print", True), + ], +) +def test_match_group_complex(input_str, expected_result, tainted): + regex = re.compile( + r"(? Date: Wed, 20 Nov 2024 14:53:00 +0100 Subject: [PATCH 189/372] chore(asm): updates waf and security rules (#11453) Update libddwaf to 1.21.0 Update security rule file to 1.13.3 Update snapshots accordingly APPSEC-55919 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/rules.json | 508 ++++++++++++------ ...waf_update_to_1.21.0-2f77adaf9ecce679.yaml | 4 + setup.py | 2 +- ...st_appsec_body_no_collection_snapshot.json | 6 +- ...appsec_cookies_no_collection_snapshot.json | 6 +- ...cessor.test_appsec_span_tags_snapshot.json | 6 +- ...appsec_span_tags_snapshot_with_errors.json | 2 +- ..._appsec_snapshots.test_appsec_enabled.json | 6 +- ..._snapshots.test_appsec_enabled_attack.json | 6 +- ...pshots.test_request_ipblock_match_403.json | 2 +- ...s.test_request_ipblock_match_403_json.json | 2 +- ...hots.test_request_ipblock_nomatch_200.json | 2 +- ...atch_403[flask_appsec_good_rules_env].json | 2 +- ..._403[flask_appsec_good_rules_env]_220.json | 2 +- ...403_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- ..._osspawn[flask_appsec_good_rules_env].json | 2 +- ...pawn[flask_appsec_good_rules_env]_220.json | 2 +- ...ossystem[flask_appsec_good_rules_env].json | 2 +- ...stem[flask_appsec_good_rules_env]_220.json | 2 +- ...enoshell[flask_appsec_good_rules_env].json | 2 +- ...hell[flask_appsec_good_rules_env]_220.json | 2 +- ...ateshell[flask_appsec_good_rules_env].json | 2 +- ...hell[flask_appsec_good_rules_env]_220.json | 2 +- ...200_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- ...403_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- 28 files changed, 393 insertions(+), 191 deletions(-) create mode 100644 releasenotes/notes/waf_update_to_1.21.0-2f77adaf9ecce679.yaml diff --git a/ddtrace/appsec/rules.json b/ddtrace/appsec/rules.json index d0e486c6731..2f53276c7e5 100644 --- a/ddtrace/appsec/rules.json +++ b/ddtrace/appsec/rules.json @@ -1,7 +1,7 @@ { "version": "2.2", "metadata": { - "rules_version": "1.13.2" + "rules_version": "1.13.3" }, "rules": [ { @@ -9,7 +9,8 @@ "name": "Block IP Addresses", "tags": { "type": "block_ip", - "category": "security_response" + "category": "security_response", + "module": "network-acl" }, "conditions": [ { @@ -34,7 +35,8 @@ "name": "Block User Addresses", "tags": { "type": "block_user", - "category": "security_response" + "category": "security_response", + "module": "authentication-acl" }, "conditions": [ { @@ -64,7 +66,8 @@ "tool_name": "Acunetix", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -98,7 +101,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -162,7 +166,8 @@ "category": "attack_attempt", "cwe": "176", "capec": "1000/255/153/267/71", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -191,7 +196,8 @@ "crs_id": "921110", "category": "attack_attempt", "cwe": "444", - "capec": "1000/210/272/220/33" + "capec": "1000/210/272/220/33", + "module": "waf" }, "conditions": [ { @@ -228,7 +234,8 @@ "crs_id": "921160", "category": "attack_attempt", "cwe": "113", - "capec": "1000/210/272/220/105" + "capec": "1000/210/272/220/105", + "module": "waf" }, "conditions": [ { @@ -263,7 +270,8 @@ "category": "attack_attempt", "cwe": "22", "capec": "1000/255/153/126", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -297,7 +305,8 @@ "category": "attack_attempt", "cwe": "22", "capec": "1000/255/153/126", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -1803,7 +1812,8 @@ "category": "attack_attempt", "cwe": "98", "capec": "1000/152/175/253/193", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -1831,7 +1841,8 @@ "crs_id": "931120", "category": "attack_attempt", "cwe": "98", - "capec": "1000/152/175/253/193" + "capec": "1000/152/175/253/193", + "module": "waf" }, "conditions": [ { @@ -1876,7 +1887,8 @@ "category": "attack_attempt", "cwe": "77", "capec": "1000/152/248/88", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2388,7 +2400,8 @@ "category": "attack_attempt", "cwe": "77", "capec": "1000/152/248/88", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2436,7 +2449,8 @@ "category": "attack_attempt", "cwe": "706", "capec": "1000/225/122/17/177", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2500,7 +2514,8 @@ "category": "attack_attempt", "cwe": "434", "capec": "1000/225/122/17/650", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2553,7 +2568,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/225/122/17/650", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2620,7 +2636,8 @@ "crs_id": "933131", "category": "attack_attempt", "cwe": "94", - "capec": "1000/225/122/17/650" + "capec": "1000/225/122/17/650", + "module": "waf" }, "conditions": [ { @@ -2665,7 +2682,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/225/122/17/650", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2709,7 +2727,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/225/122/17/650", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2799,7 +2818,8 @@ "crs_id": "933160", "category": "attack_attempt", "cwe": "94", - "capec": "1000/225/122/17/650" + "capec": "1000/225/122/17/650", + "module": "waf" }, "conditions": [ { @@ -2824,7 +2844,7 @@ "address": "graphql.server.resolver" } ], - "regex": "\\b(?:s(?:e(?:t(?:_(?:e(?:xception|rror)_handler|magic_quotes_runtime|include_path)|defaultstub)|ssion_s(?:et_save_handler|tart))|qlite_(?:(?:(?:unbuffered|single|array)_)?query|create_(?:aggregate|function)|p?open|exec)|tr(?:eam_(?:context_create|socket_client)|ipc?slashes|rev)|implexml_load_(?:string|file)|ocket_c(?:onnect|reate)|h(?:ow_sourc|a1_fil)e|pl_autoload_register|ystem)|p(?:r(?:eg_(?:replace(?:_callback(?:_array)?)?|match(?:_all)?|split)|oc_(?:(?:terminat|clos|nic)e|get_status|open)|int_r)|o(?:six_(?:get(?:(?:e[gu]|g)id|login|pwnam)|mk(?:fifo|nod)|ttyname|kill)|pen)|hp(?:_(?:strip_whitespac|unam)e|version|info)|g_(?:(?:execut|prepar)e|connect|query)|a(?:rse_(?:ini_file|str)|ssthru)|utenv)|r(?:unkit_(?:function_(?:re(?:defin|nam)e|copy|add)|method_(?:re(?:defin|nam)e|copy|add)|constant_(?:redefine|add))|e(?:(?:gister_(?:shutdown|tick)|name)_function|ad(?:(?:gz)?file|_exif_data|dir))|awurl(?:de|en)code)|i(?:mage(?:createfrom(?:(?:jpe|pn)g|x[bp]m|wbmp|gif)|(?:jpe|pn)g|g(?:d2?|if)|2?wbmp|xbm)|s_(?:(?:(?:execut|write?|read)ab|fi)le|dir)|ni_(?:get(?:_all)?|set)|terator_apply|ptcembed)|g(?:et(?:_(?:c(?:urrent_use|fg_va)r|meta_tags)|my(?:[gpu]id|inode)|(?:lastmo|cw)d|imagesize|env)|z(?:(?:(?:defla|wri)t|encod|fil)e|compress|open|read)|lob)|a(?:rray_(?:u(?:intersect(?:_u?assoc)?|diff(?:_u?assoc)?)|intersect_u(?:assoc|key)|diff_u(?:assoc|key)|filter|reduce|map)|ssert(?:_options)?|tob)|h(?:tml(?:specialchars(?:_decode)?|_entity_decode|entities)|(?:ash(?:_(?:update|hmac))?|ighlight)_file|e(?:ader_register_callback|x2bin))|f(?:i(?:le(?:(?:[acm]tim|inod)e|(?:_exist|perm)s|group)?|nfo_open)|tp_(?:nb_(?:ge|pu)|connec|ge|pu)t|(?:unction_exis|pu)ts|write|open)|o(?:b_(?:get_(?:c(?:ontents|lean)|flush)|end_(?:clean|flush)|clean|flush|start)|dbc_(?:result(?:_all)?|exec(?:ute)?|connect)|pendir)|m(?:b_(?:ereg(?:_(?:replace(?:_callback)?|match)|i(?:_replace)?)?|parse_str)|(?:ove_uploaded|d5)_file|ethod_exists|ysql_query|kdir)|e(?:x(?:if_(?:t(?:humbnail|agname)|imagetype|read_data)|ec)|scapeshell(?:arg|cmd)|rror_reporting|val)|c(?:url_(?:file_create|exec|init)|onvert_uuencode|reate_function|hr)|u(?:n(?:serialize|pack)|rl(?:de|en)code|[ak]?sort)|b(?:(?:son_(?:de|en)|ase64_en)code|zopen|toa)|(?:json_(?:de|en)cod|debug_backtrac|tmpfil)e|var_dump)(?:\\s|/\\*.*\\*/|//.*|#.*|\\\"|')*\\((?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?,)*(?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?)?\\)", + "regex": "\\b(?:s(?:e(?:t(?:_(?:e(?:xception|rror)_handler|magic_quotes_runtime|include_path)|defaultstub)|ssion_s(?:et_save_handler|tart))|qlite_(?:(?:(?:unbuffered|single|array)_)?query|create_(?:aggregate|function)|p?open|exec)|tr(?:eam_(?:context_create|socket_client)|ipc?slashes|rev)|implexml_load_(?:string|file)|ocket_c(?:onnect|reate)|h(?:ow_sourc|a1_fil)e|pl_autoload_register|ystem)|p(?:r(?:eg_(?:replace(?:_callback(?:_array)?)?|match(?:_all)?|split)|oc_(?:(?:terminat|clos|nic)e|get_status|open)|int_r)|o(?:six_(?:get(?:(?:e[gu]|g)id|login|pwnam)|mk(?:fifo|nod)|ttyname|kill)|pen)|hp(?:_(?:strip_whitespac|unam)e|version|info)|g_(?:(?:execut|prepar)e|connect|query)|a(?:rse_(?:ini_file|str)|ssthru)|utenv)|r(?:unkit_(?:function_(?:re(?:defin|nam)e|copy|add)|method_(?:re(?:defin|nam)e|copy|add)|constant_(?:redefine|add))|e(?:(?:gister_(?:shutdown|tick)|name)_function|ad(?:(?:gz)?file|_exif_data|dir))|awurl(?:de|en)code)|i(?:mage(?:createfrom(?:(?:jpe|pn)g|x[bp]m|wbmp|gif)|(?:jpe|pn)g|g(?:d2?|if)|2?wbmp|xbm)|s_(?:(?:(?:execut|write?|read)ab|fi)le|dir)|ni_(?:get(?:_all)?|set)|terator_apply|ptcembed)|g(?:et(?:_(?:c(?:urrent_use|fg_va)r|meta_tags)|my(?:[gpu]id|inode)|(?:lastmo|cw)d|imagesize|env)|z(?:(?:(?:defla|wri)t|encod|fil)e|compress|open|read)|lob)|a(?:rray_(?:u(?:intersect(?:_u?assoc)?|diff(?:_u?assoc)?)|intersect_u(?:assoc|key)|diff_u(?:assoc|key)|filter|reduce|map)|ssert(?:_options)?|tob)|h(?:tml(?:specialchars(?:_decode)?|_entity_decode|entities)|(?:ash(?:_(?:update|hmac))?|ighlight)_file|e(?:ader_register_callback|x2bin))|f(?:i(?:le(?:(?:[acm]tim|inod)e|(?:_exist|perm)s|group)?|nfo_open)|tp_(?:nb_(?:ge|pu)|connec|ge|pu)t|(?:unction_exis|pu)ts|write|open)|o(?:b_(?:get_(?:c(?:ontents|lean)|flush)|end_(?:clean|flush)|clean|flush|start)|dbc_(?:result(?:_all)?|exec(?:ute)?|connect)|pendir)|m(?:b_(?:ereg(?:_(?:replace(?:_callback)?|match)|i(?:_replace)?)?|parse_str)|(?:ove_uploaded|d5)_file|ethod_exists|ysql_query|kdir)|e(?:x(?:if_(?:t(?:humbnail|agname)|imagetype|read_data)|ec)|scapeshell(?:arg|cmd)|rror_reporting|val)|c(?:url_(?:file_create|exec|init)|onvert_uuencode|reate_function|hr)|u(?:n(?:serialize|pack)|rl(?:de|en)code|[ak]?sort)|b(?:(?:son_(?:de|en)|ase64_en)code|zopen|toa)|(?:json_(?:de|en)cod|debug_backtrac|tmpfil)e|var_dump)(?:\\s|/\\*.*\\*/|//.*|#.*|\\\"|')*\\((?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?,)*(?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?)?\\)\\s*(?:[;\\.)}\\]|\\\\]|\\?>|%>|$)", "options": { "case_sensitive": true, "min_length": 5 @@ -2844,7 +2864,8 @@ "category": "attack_attempt", "cwe": "502", "capec": "1000/152/586", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -2891,7 +2912,8 @@ "crs_id": "933200", "category": "attack_attempt", "cwe": "502", - "capec": "1000/152/586" + "capec": "1000/152/586", + "module": "waf" }, "conditions": [ { @@ -2937,7 +2959,8 @@ "crs_id": "934100", "category": "attack_attempt", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -2982,7 +3005,8 @@ "category": "attack_attempt", "confidence": "1", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -3024,7 +3048,8 @@ "category": "attack_attempt", "cwe": "80", "capec": "1000/152/242/63/591", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3081,7 +3106,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3140,7 +3166,8 @@ "category": "attack_attempt", "cwe": "84", "capec": "1000/152/242/63/591/244", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3199,7 +3226,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3257,7 +3285,8 @@ "crs_id": "941180", "category": "attack_attempt", "cwe": "79", - "capec": "1000/152/242/63/591" + "capec": "1000/152/242/63/591", + "module": "waf" }, "conditions": [ { @@ -3311,7 +3340,8 @@ "category": "attack_attempt", "cwe": "80", "capec": "1000/152/242/63/591", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3358,7 +3388,8 @@ "category": "attack_attempt", "cwe": "80", "capec": "1000/152/242/63/591", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3405,7 +3436,8 @@ "category": "attack_attempt", "cwe": "80", "capec": "1000/152/242/63/591", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3452,7 +3484,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3498,7 +3531,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3545,7 +3579,8 @@ "crs_id": "941270", "category": "attack_attempt", "cwe": "83", - "capec": "1000/152/242/63/591/243" + "capec": "1000/152/242/63/591/243", + "module": "waf" }, "conditions": [ { @@ -3588,7 +3623,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3634,7 +3670,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3680,7 +3717,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3726,7 +3764,8 @@ "category": "attack_attempt", "cwe": "87", "capec": "1000/152/242/63/591/199", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3770,7 +3809,8 @@ "crs_id": "941360", "category": "attack_attempt", "cwe": "87", - "capec": "1000/152/242/63/591/199" + "capec": "1000/152/242/63/591/199", + "module": "waf" }, "conditions": [ { @@ -3815,7 +3855,8 @@ "category": "attack_attempt", "confidence": "1", "cwe": "79", - "capec": "1000/152/242/63/591" + "capec": "1000/152/242/63/591", + "module": "waf" }, "conditions": [ { @@ -3859,7 +3900,8 @@ "crs_id": "942100", "category": "attack_attempt", "cwe": "89", - "capec": "1000/152/248/66" + "capec": "1000/152/248/66", + "module": "waf" }, "conditions": [ { @@ -3898,7 +3940,8 @@ "category": "attack_attempt", "cwe": "89", "capec": "1000/152/248/66/7", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3943,7 +3986,8 @@ "category": "attack_attempt", "cwe": "89", "capec": "1000/152/248/66/7", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -3986,7 +4030,8 @@ "crs_id": "942250", "category": "attack_attempt", "cwe": "89", - "capec": "1000/152/248/66" + "capec": "1000/152/248/66", + "module": "waf" }, "conditions": [ { @@ -4030,7 +4075,8 @@ "crs_id": "942270", "category": "attack_attempt", "cwe": "89", - "capec": "1000/152/248/66" + "capec": "1000/152/248/66", + "module": "waf" }, "conditions": [ { @@ -4074,7 +4120,8 @@ "category": "attack_attempt", "cwe": "89", "capec": "1000/152/248/66/7", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4117,7 +4164,8 @@ "crs_id": "942290", "category": "attack_attempt", "cwe": "943", - "capec": "1000/152/248/676" + "capec": "1000/152/248/676", + "module": "waf" }, "conditions": [ { @@ -4163,7 +4211,8 @@ "crs_id": "942360", "category": "attack_attempt", "cwe": "89", - "capec": "1000/152/248/66/470" + "capec": "1000/152/248/66/470", + "module": "waf" }, "conditions": [ { @@ -4206,7 +4255,8 @@ "crs_id": "942500", "category": "attack_attempt", "cwe": "89", - "capec": "1000/152/248/66" + "capec": "1000/152/248/66", + "module": "waf" }, "conditions": [ { @@ -4251,7 +4301,8 @@ "category": "attack_attempt", "cwe": "384", "capec": "1000/225/21/593/61", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4296,7 +4347,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/152/242", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4344,7 +4396,8 @@ "type": "java_code_injection", "category": "attack_attempt", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -4391,7 +4444,8 @@ "crs_id": "944130", "category": "attack_attempt", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -4529,7 +4583,8 @@ "type": "nosql_injection", "category": "attack_attempt", "cwe": "943", - "capec": "1000/152/248/676" + "capec": "1000/152/248/676", + "module": "waf" }, "conditions": [ { @@ -4573,7 +4628,8 @@ "type": "java_code_injection", "category": "attack_attempt", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -4619,7 +4675,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/152/242", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4695,7 +4752,8 @@ "category": "attack_attempt", "cwe": "1321", "capec": "1000/152/242", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4725,7 +4783,8 @@ "category": "attack_attempt", "cwe": "1321", "capec": "1000/152/242", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4769,7 +4828,8 @@ "category": "attack_attempt", "cwe": "1336", "capec": "1000/152/242/19", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4813,7 +4873,8 @@ "tool_name": "BurpCollaborator", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4857,7 +4918,8 @@ "tool_name": "Qualys", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -4901,7 +4963,8 @@ "tool_name": "Probely", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -4944,7 +5007,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -4987,7 +5051,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5031,7 +5096,8 @@ "tool_name": "Rapid7", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5075,7 +5141,8 @@ "tool_name": "interact.sh", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5119,7 +5186,8 @@ "tool_name": "Netsparker", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5167,7 +5235,8 @@ "tool_name": "WhiteHatSecurity", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5215,7 +5284,8 @@ "tool_name": "Nessus", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5263,7 +5333,8 @@ "tool_name": "Watchtowr", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5311,7 +5382,8 @@ "tool_name": "AppCheckNG", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5358,7 +5430,8 @@ "category": "attack_attempt", "cwe": "287", "capec": "1000/225/115", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5392,7 +5465,8 @@ "category": "attack_attempt", "cwe": "98", "capec": "1000/152/175/253/193", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5436,7 +5510,8 @@ "category": "attack_attempt", "cwe": "77", "capec": "1000/152/248/88", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -5483,7 +5558,8 @@ "category": "attack_attempt", "cwe": "91", "capec": "1000/152/248/250", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5521,7 +5597,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5579,7 +5656,8 @@ "category": "attack_attempt", "cwe": "83", "capec": "1000/152/242/63/591/243", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5866,7 +5944,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5908,7 +5987,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5950,7 +6030,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -5992,7 +6073,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6034,7 +6116,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6059,7 +6142,7 @@ "address": "server.request.uri.raw" } ], - "regex": "\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)", + "regex": "\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([?#&/]|$)", "options": { "case_sensitive": false } @@ -6076,7 +6159,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6118,7 +6202,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6160,7 +6245,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6202,7 +6288,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -6276,7 +6363,7 @@ } ] }, - "operator": "lfi_detector" + "operator": "lfi_detector@v2" } ], "transformers": [], @@ -6286,7 +6373,7 @@ }, { "id": "rasp-932-100", - "name": "Command injection exploit", + "name": "Shell command injection exploit", "tags": { "type": "command_injection", "category": "vulnerability_trigger", @@ -6332,6 +6419,54 @@ "stack_trace" ] }, + { + "id": "rasp-932-110", + "name": "OS command injection exploit", + "tags": { + "type": "command_injection", + "category": "vulnerability_trigger", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "0", + "module": "rasp" + }, + "conditions": [ + { + "parameters": { + "resource": [ + { + "address": "server.sys.exec.cmd" + } + ], + "params": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ] + }, + "operator": "cmdi_detector" + } + ], + "transformers": [], + "on_match": [ + "stack_trace" + ] + }, { "id": "rasp-934-100", "name": "Server-side request forgery exploit", @@ -6438,7 +6573,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6482,7 +6618,8 @@ "type": "js_code_injection", "category": "attack_attempt", "cwe": "94", - "capec": "1000/152/242" + "capec": "1000/152/242", + "module": "waf" }, "conditions": [ { @@ -6527,7 +6664,8 @@ "category": "attack_attempt", "cwe": "78", "capec": "1000/152/248/88", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6570,7 +6708,8 @@ "category": "attack_attempt", "cwe": "78", "capec": "1000/152/248/88", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6615,7 +6754,8 @@ "category": "attack_attempt", "cwe": "78", "capec": "1000/152/248/88", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6658,7 +6798,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6701,7 +6842,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -6743,7 +6885,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -6785,7 +6928,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6828,7 +6972,8 @@ "category": "attack_attempt", "cwe": "918", "capec": "1000/225/115/664", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -6870,7 +7015,8 @@ "category": "attack_attempt", "cwe": "94", "capec": "1000/152/242", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6916,7 +7062,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Joomla exploitation tool", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6945,7 +7092,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nessus", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -6974,7 +7122,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Arachni", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7003,7 +7152,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Jorgee", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7032,7 +7182,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Probely", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -7061,7 +7212,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Metis", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7090,7 +7242,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "SQLPowerInjector", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7119,7 +7272,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "N-Stealth", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7148,7 +7302,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Brutus", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7176,7 +7331,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7205,7 +7361,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Netsparker", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -7234,7 +7391,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "JAASCois", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7263,7 +7421,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nsauditor", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7292,7 +7451,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Paros", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7321,7 +7481,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "DirBuster", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7350,7 +7511,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Pangolin", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7379,7 +7541,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Qualys", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -7408,7 +7571,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "SQLNinja", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7437,7 +7601,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nikto", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7466,7 +7631,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "BlackWidow", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7495,7 +7661,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Grendel-Scan", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7524,7 +7691,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Havij", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7553,7 +7721,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "w3af", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7582,7 +7751,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nmap", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7611,7 +7781,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nessus", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7640,7 +7811,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "EvilScanner", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7669,7 +7841,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "WebFuck", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7698,7 +7871,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "OpenVAS", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7727,7 +7901,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Spider-Pig", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7756,7 +7931,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Zgrab", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7785,7 +7961,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Zmeu", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7814,7 +7991,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "GoogleSecurityScanner", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -7843,7 +8021,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Commix", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7872,7 +8051,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Gobuster", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7901,7 +8081,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "CGIchk", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7930,7 +8111,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "FFUF", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7959,7 +8141,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nuclei", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -7988,7 +8171,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Tsunami", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8017,7 +8201,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Nimbostratus", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8046,7 +8231,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Datadog Canary Test", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8081,7 +8267,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Datadog Canary Test", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8119,7 +8306,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "AlertLogic", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -8148,7 +8336,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "wfuzz", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8177,7 +8366,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Detectify", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -8206,7 +8396,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "BSQLBF", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8235,7 +8426,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "masscan", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8264,7 +8456,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "WPScan", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8293,7 +8486,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Aon", - "confidence": "0" + "confidence": "0", + "module": "waf" }, "conditions": [ { @@ -8322,7 +8516,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "feroxbuster", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8350,7 +8545,8 @@ "category": "attack_attempt", "cwe": "200", "capec": "1000/118/169", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8382,7 +8578,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "SQLmap", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { @@ -8411,7 +8608,8 @@ "cwe": "200", "capec": "1000/118/169", "tool_name": "Skipfish", - "confidence": "1" + "confidence": "1", + "module": "waf" }, "conditions": [ { diff --git a/releasenotes/notes/waf_update_to_1.21.0-2f77adaf9ecce679.yaml b/releasenotes/notes/waf_update_to_1.21.0-2f77adaf9ecce679.yaml new file mode 100644 index 00000000000..b0017720b4f --- /dev/null +++ b/releasenotes/notes/waf_update_to_1.21.0-2f77adaf9ecce679.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + ASM: This upgrades libddwaf to 1.21.0 and security rule file to 1.13.3 diff --git a/setup.py b/setup.py index ce1aa685596..72213c11868 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ CURRENT_OS = platform.system() -LIBDDWAF_VERSION = "1.20.1" +LIBDDWAF_VERSION = "1.21.0" RUST_MINIMUM_VERSION = "1.71" # Safe guess: 1.71 is about a year old as of 2024-07-03 diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index 7910618da6f..4f093b82c80 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -8,9 +8,9 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.2", + "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", @@ -23,7 +23,7 @@ "metrics": { "_dd.appsec.enabled": 1.0, "_dd.appsec.event_rules.error_count": 0, - "_dd.appsec.event_rules.loaded": 158, + "_dd.appsec.event_rules.loaded": 159, "_dd.appsec.waf.duration": 204.672, "_dd.appsec.waf.duration_ext": 280.3802490234375, "_dd.top_level": 1, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index ca97ab0281e..cbdf4ac389d 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -8,9 +8,9 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.2", + "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", @@ -23,7 +23,7 @@ "metrics": { "_dd.appsec.enabled": 1.0, "_dd.appsec.event_rules.error_count": 0, - "_dd.appsec.event_rules.loaded": 158, + "_dd.appsec.event_rules.loaded": 159, "_dd.appsec.waf.duration": 103.238, "_dd.appsec.waf.duration_ext": 174.04556274414062, "_dd.top_level": 1, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index 3137305e022..4908df4eed0 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -8,9 +8,9 @@ "parent_id": 0, "type": "web", "meta": { - "_dd.appsec.event_rules.version": "1.13.2", + "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "tests.appsec.appsec", "_dd.origin": "appsec", "_dd.p.appsec": "1", @@ -25,7 +25,7 @@ "metrics": { "_dd.appsec.enabled": 1.0, "_dd.appsec.event_rules.error_count": 0, - "_dd.appsec.event_rules.loaded": 158, + "_dd.appsec.event_rules.loaded": 159, "_dd.appsec.waf.duration": 126.022, "_dd.appsec.waf.duration_ext": 203.3710479736328, "_dd.top_level": 1, diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index def21246eeb..60c918bdc2f 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "tests.appsec.appsec", "_dd.p.dm": "-0", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index b1a8a8b0bd4..344f63429a7 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -9,8 +9,8 @@ "type": "web", "error": 0, "meta": { - "_dd.appsec.event_rules.version": "1.13.2", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.event_rules.version": "1.13.3", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", @@ -42,7 +42,7 @@ "metrics": { "_dd.appsec.enabled": 1.0, "_dd.appsec.event_rules.error_count": 0, - "_dd.appsec.event_rules.loaded": 158, + "_dd.appsec.event_rules.loaded": 159, "_dd.appsec.waf.duration": 96.626, "_dd.appsec.waf.duration_ext": 147.81951904296875, "_dd.measured": 1, diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index 2da6e3029aa..02956faa875 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -9,9 +9,9 @@ "type": "web", "error": 0, "meta": { - "_dd.appsec.event_rules.version": "1.13.2", + "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", @@ -45,7 +45,7 @@ "metrics": { "_dd.appsec.enabled": 1.0, "_dd.appsec.event_rules.error_count": 0, - "_dd.appsec.event_rules.loaded": 158, + "_dd.appsec.event_rules.loaded": 159, "_dd.appsec.waf.duration": 236.874, "_dd.appsec.waf.duration_ext": 339.26963806152344, "_dd.measured": 1, diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json index 91b403fd793..c5ce0d7abf9 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":10192376353237234254}]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json index 80badf55332..467d576ad85 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":865087550764298227}]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json index bc4695d1779..52a99d13b63 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json index 28cc19763ff..40295cc8b37 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json index cb9c1ce72de..32d8e8e4dde 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json index 7dbeab81e7a..a8d620726eb 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json index eb86dfdf683..9e98ab1fa2d 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json index 94d43de577b..d9bf74ab521 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json index 481dab6544d..4e3c507fb24 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json index f0d79286696..a53a26279ee 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json index 3ea6ae304d8..89a294b6e8d 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json index 5cc68d6a6bb..5265382cf59 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json index d37bbd839d7..ae473681205 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json index 5b23316cb90..2eb36d25a38 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json index ba344ea3ce4..800d73259aa 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json index d7a371db853..d29fdcda126 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json index eb1c40a7939..e099df45417 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index 345427444ea..5a0c8e301a5 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index 2aa7846ca97..806e0de6295 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.20.1", + "_dd.appsec.waf.version": "1.21.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", From 0c1953cd0c0915cc88cebd5a86133d4b6caffce4 Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:09:00 -0500 Subject: [PATCH 190/372] chore(testing): fix snapshot failures (#11454) Fixes snapshot failures. these tests were previously passing due to differences in run commands between feature branches and main. The difference between test scripts has been reconciled. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/run-test-suite | 2 +- ...b.aiomysql.test_aiomysql.test_queries.json | 4 +-- ...asyncpg.test_asyncpg.test_bad_connect.json | 2 +- ...b.asyncpg.test_asyncpg.test_bad_query.json | 4 +-- ...rib.asyncpg.test_asyncpg.test_connect.json | 2 +- ....test_asyncpg.test_connection_methods.json | 12 ++++----- ...trib.asyncpg.test_asyncpg.test_cursor.json | 12 ++++----- ...yncpg.test_asyncpg.test_cursor_manual.json | 12 ++++----- ...b.asyncpg.test_asyncpg.test_parenting.json | 10 +++---- ...trib.asyncpg.test_asyncpg.test_select.json | 4 +-- ...est_asyncpg.test_service_override_pin.json | 4 +-- ..._handlers[ClassHandler-class_handler].json | 4 +-- ...andlers[StaticHandler-static_handler].json | 4 +-- ...d_handlers[handler2-instance_handler].json | 4 +-- ...[handler3-instance_handler_with_code].json | 4 +-- ...a.test_continue_on_early_trace_ending.json | 2 +- ...da.test_aws_lambda.test_file_patching.json | 2 +- ....test_aws_lambda.test_module_patching.json | 2 +- ..._aws_lambda.test_timeout_traces[-100].json | 4 +-- ...t_aws_lambda.test_timeout_traces[100].json | 4 +-- ...st_aws_lambda.test_timeout_traces[10].json | 4 +-- ...t_aws_lambda.test_timeout_traces[200].json | 4 +-- ....test_aws_payload_tagging_eventbridge.json | 18 ++++++------- ...Test.test_aws_payload_tagging_kinesis.json | 10 +++---- ...ocoreTest.test_aws_payload_tagging_s3.json | 10 +++---- ...aws_payload_tagging_s3_invalid_config.json | 10 +++---- ...t_aws_payload_tagging_s3_valid_config.json | 10 +++---- ...coreTest.test_aws_payload_tagging_sns.json | 14 +++++----- ..._aws_payload_tagging_sns_valid_config.json | 14 +++++----- ...coreTest.test_aws_payload_tagging_sqs.json | 8 +++--- ...otocore.test_bedrock.test_ai21_invoke.json | 2 +- ...re.test_bedrock.test_amazon_embedding.json | 2 +- ...ocore.test_bedrock.test_amazon_invoke.json | 2 +- ...est_bedrock.test_amazon_invoke_stream.json | 2 +- ...re.test_bedrock.test_anthropic_invoke.json | 2 +- ..._bedrock.test_anthropic_invoke_stream.json | 2 +- ...bedrock.test_anthropic_message_invoke.json | 2 +- ....test_anthropic_message_invoke_stream.json | 2 +- ...botocore.test_bedrock.test_auth_error.json | 2 +- ...re.test_bedrock.test_cohere_embedding.json | 2 +- ...drock.test_cohere_invoke_multi_output.json | 2 +- ...rock.test_cohere_invoke_single_output.json | 2 +- ...est_cohere_invoke_stream_multi_output.json | 2 +- ..._cohere_invoke_stream_multiple_output.json | 2 +- ...st_cohere_invoke_stream_single_output.json | 2 +- ...otocore.test_bedrock.test_meta_invoke.json | 2 +- ....test_bedrock.test_meta_invoke_stream.json | 2 +- ...botocore.test_bedrock.test_read_error.json | 2 +- ...e.test_bedrock.test_read_stream_error.json | 2 +- ...ore.test_bedrock.test_readlines_error.json | 2 +- ...analytics_with_rate_snapshot_post_1_1.json | 2 +- ..._analytics_with_rate_snapshot_pre_1_1.json | 2 +- ...lytics_without_rate_snapshot_post_1_1.json | 2 +- ...alytics_without_rate_snapshot_pre_1_1.json | 2 +- ...mariadb.test_commit_snapshot_post_1_1.json | 2 +- ..._mariadb.test_commit_snapshot_pre_1_1.json | 2 +- ...query_many_fetchall_snapshot_post_1_1.json | 6 ++--- ..._query_many_fetchall_snapshot_pre_1_1.json | 6 ++--- ...adb.test_query_proc_snapshot_post_1_1.json | 2 +- ...iadb.test_query_proc_snapshot_pre_1_1.json | 2 +- ...veral_rows_fetchall_snapshot_post_1_1.json | 4 +-- ...everal_rows_fetchall_snapshot_pre_1_1.json | 4 +-- ...y_with_several_rows_snapshot_post_1_1.json | 2 +- ...ry_with_several_rows_snapshot_pre_1_1.json | 2 +- ...ple_malformed_query_snapshot_post_1_1.json | 2 +- ...mple_malformed_query_snapshot_pre_1_1.json | 2 +- ...mple_query_fetchall_snapshot_post_1_1.json | 4 +-- ...imple_query_fetchall_snapshot_pre_1_1.json | 4 +-- ...b.test_simple_query_snapshot_post_1_1.json | 2 +- ...db.test_simple_query_snapshot_pre_1_1.json | 2 +- ..._psycopg.test_composed_query_encoding.json | 2 +- ..._psycopg.test_composed_query_encoding.json | 2 +- ...ext.test_otel_ddtrace_mixed_parenting.json | 12 ++++----- ...test_context.test_otel_multithreading.json | 26 +++++++++---------- ...test_context.test_otel_span_parenting.json | 12 ++++----- ...t_context.test_otel_trace_across_fork.json | 4 +-- ...t.test_otel_trace_multiple_coroutines.json | 10 +++---- ...cisions_across_processes[manual.drop].json | 4 +-- ...cisions_across_processes[manual.keep].json | 4 +-- ...y.test_span.test_otel_span_attributes.json | 6 ++--- ..._span_attributes_overrides[override0].json | 2 +- ..._span_attributes_overrides[override1].json | 2 +- ..._span_attributes_overrides[override2].json | 2 +- ..._span_attributes_overrides[override3].json | 2 +- ..._span_attributes_overrides[override4].json | 2 +- ..._span_attributes_overrides[override5].json | 2 +- ...metry.test_span.test_otel_span_events.json | 4 +-- ...lemetry.test_span.test_otel_span_kind.json | 10 +++---- ...race_with_flask_app[with_ddtrace_run].json | 2 +- ...sk_app[with_opentelemetry_instrument].json | 2 +- ..._start_current_span_with_default_args.json | 2 +- ...art_current_span_without_default_args.json | 4 +-- ...est_otel_start_span_ignore_exceptions.json | 2 +- ...test_otel_start_span_record_exception.json | 4 +-- ..._otel_start_span_without_default_args.json | 4 +-- 95 files changed, 212 insertions(+), 212 deletions(-) diff --git a/scripts/run-test-suite b/scripts/run-test-suite index 6ba00939113..cca6bb5262e 100755 --- a/scripts/run-test-suite +++ b/scripts/run-test-suite @@ -58,7 +58,7 @@ fi for hash in ${RIOT_HASHES[@]}; do echo "Running riot hash: $hash" - ($DDTEST_CMD riot -P -v run --exitfirst --pass-env -s $hash $DDTRACE_FLAG $COVERAGE_FLAG) + ($DDTEST_CMD riot -P -v run --exitfirst --pass-env -s $hash $COVERAGE_FLAG $DDTRACE_FLAG) exit_code=$? if [ $exit_code -ne 0 ] ; then if [[ -v CIRCLECI ]]; then diff --git a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_queries.json b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_queries.json index 4461a948a5e..595dabed064 100644 --- a/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_queries.json +++ b/tests/snapshots/tests.contrib.aiomysql.test_aiomysql.test_queries.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiomysql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aiomysql", @@ -46,7 +46,7 @@ "type": "sql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.aiomysql", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "aiomysql", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_connect.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_connect.json index e2cb163fa45..f6369026867 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_connect.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_connect.json @@ -9,7 +9,7 @@ "type": "sql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_query.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_query.json index 5c77caddc0b..f2fe6bde0e6 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_query.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_bad_query.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connect.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connect.json index 7502bce1b4d..fdee89d833e 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connect.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connect.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connection_methods.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connection_methods.json index 0e209e9d88c..3be421fa058 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connection_methods.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_connection_methods.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -78,7 +78,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -113,7 +113,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -148,7 +148,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -183,7 +183,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor.json index 4e239d690b7..4c0c68edffb 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -78,7 +78,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -113,7 +113,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -148,7 +148,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -183,7 +183,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor_manual.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor_manual.json index e978bb0bfca..7a258b520ef 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor_manual.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_cursor_manual.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -78,7 +78,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -113,7 +113,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -148,7 +148,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -183,7 +183,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_parenting.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_parenting.json index 5681105f031..4741a735316 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_parenting.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_parenting.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -35,7 +35,7 @@ [ { "name": "parent", - "service": "", + "service": "tests.contrib.asyncpg", "resource": "parent", "trace_id": 1, "span_id": 1, @@ -67,7 +67,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.tid": "654a694400000000", "component": "asyncpg", "db.name": "postgres", @@ -88,7 +88,7 @@ [ { "name": "parent2", - "service": "", + "service": "tests.contrib.asyncpg", "resource": "parent2", "trace_id": 2, "span_id": 1, @@ -121,7 +121,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_select.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_select.json index f3eada8ef0c..256dbd38d9c 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_select.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_select.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_service_override_pin.json b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_service_override_pin.json index 1fdcddaad18..2fb6ce89cb8 100644 --- a/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_service_override_pin.json +++ b/tests/snapshots/tests.contrib.asyncpg.test_asyncpg.test_service_override_pin.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", @@ -43,7 +43,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.asyncpg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "asyncpg", diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[ClassHandler-class_handler].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[ClassHandler-class_handler].json index 9bc14104eb0..8270fac7072 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[ClassHandler-class_handler].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[ClassHandler-class_handler].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "result-trace", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "result-trace", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[StaticHandler-static_handler].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[StaticHandler-static_handler].json index 3ba37acaa15..0b0f44b159f 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[StaticHandler-static_handler].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[StaticHandler-static_handler].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "result-trace", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "result-trace", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler2-instance_handler].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler2-instance_handler].json index d0923ab43e8..b10aa57f5b2 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler2-instance_handler].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler2-instance_handler].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "result-trace", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "result-trace", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler3-instance_handler_with_code].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler3-instance_handler_with_code].json index 2a860f74f6d..f5f64bfff12 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler3-instance_handler_with_code].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_class_based_handlers[handler3-instance_handler_with_code].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "result-trace", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "result-trace", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_continue_on_early_trace_ending.json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_continue_on_early_trace_ending.json index a6c8482b8fb..96065062d4a 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_continue_on_early_trace_ending.json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_continue_on_early_trace_ending.json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_file_patching.json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_file_patching.json index e1afedf7869..529c9949036 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_file_patching.json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_file_patching.json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_module_patching.json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_module_patching.json index 9fda1494974..9dc61d86e72 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_module_patching.json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_module_patching.json @@ -1,7 +1,7 @@ [[ { "name": "result-trace", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "result-trace", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[-100].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[-100].json index 03cc6a3a12e..423db77f621 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[-100].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[-100].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ }, { "name": "timeout", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "timeout", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[100].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[100].json index b04f259c054..731395feb54 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[100].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[100].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ }, { "name": "timeout", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "timeout", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[10].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[10].json index 087bc416782..3854c247fbe 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[10].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[10].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ }, { "name": "timeout", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "timeout", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[200].json b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[200].json index 41e243bd63d..232f9178fa1 100644 --- a/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[200].json +++ b/tests/snapshots/tests.contrib.aws_lambda.test_aws_lambda.test_timeout_traces[200].json @@ -1,7 +1,7 @@ [[ { "name": "aws.lambda", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "aws.lambda", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ }, { "name": "timeout", - "service": "", + "service": "tests.contrib.aws_lambda", "resource": "timeout", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json index 4a9752f5e7b..f0d63e3ef46 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_eventbridge.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219200000000", "aws.agent": "botocore", @@ -64,7 +64,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219300000000", "aws.agent": "botocore", @@ -102,7 +102,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -140,7 +140,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -177,7 +177,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -216,7 +216,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -254,7 +254,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -292,7 +292,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", @@ -329,7 +329,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725219100000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json index 5747ae5cb7f..525c5920995 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_kinesis.json @@ -1,7 +1,7 @@ [[ { "name": "kinesis.manual_span", - "service": "", + "service": "tests.contrib.botocore", "resource": "kinesis.manual_span", "trace_id": 0, "span_id": 1, @@ -33,7 +33,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "aws.agent": "botocore", "aws.kinesis.stream_name": "test", "aws.operation": "CreateStream", @@ -69,7 +69,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "aws.agent": "botocore", "aws.kinesis.stream_name": "test", "aws.operation": "PutRecords", @@ -109,7 +109,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725212f00000000", "aws.agent": "botocore", @@ -146,7 +146,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725212f00000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json index 123984f0989..2ba6104f430 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3.json @@ -9,7 +9,7 @@ "type": "http", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "67251ffe00000000", "aws.agent": "botocore", @@ -47,7 +47,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "67251ffc00000000", "aws.agent": "botocore", @@ -88,7 +88,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "67251ffd00000000", "aws.agent": "botocore", @@ -129,7 +129,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "67251ffc00000000", "aws.agent": "botocore", @@ -166,7 +166,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "67251ffc00000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json index 12d47eb2b2e..c709e1e5bea 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_invalid_config.json @@ -9,7 +9,7 @@ "type": "http", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce68500000000", "aws.agent": "botocore", @@ -47,7 +47,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce68400000000", "aws.agent": "botocore", @@ -84,7 +84,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce68400000000", "aws.agent": "botocore", @@ -123,7 +123,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce68400000000", "aws.agent": "botocore", @@ -164,7 +164,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce68500000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json index e33bc31123e..f2f0a59b243 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_s3_valid_config.json @@ -9,7 +9,7 @@ "type": "http", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce67a00000000", "aws.agent": "botocore", @@ -47,7 +47,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce67900000000", "aws.agent": "botocore", @@ -88,7 +88,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce67900000000", "aws.agent": "botocore", @@ -129,7 +129,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce67900000000", "aws.agent": "botocore", @@ -166,7 +166,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce67900000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json index 25f05615402..36416e7dcde 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229500000000", "aws.agent": "botocore", @@ -62,7 +62,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229400000000", "aws.agent": "botocore", @@ -117,7 +117,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229300000000", "aws.agent": "botocore", @@ -214,7 +214,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229100000000", "aws.agent": "botocore", @@ -264,7 +264,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229200000000", "aws.agent": "botocore", @@ -319,7 +319,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229100000000", "aws.agent": "botocore", @@ -356,7 +356,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6725229100000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json index 095f8179618..5a060e74f5e 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sns_valid_config.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8ac00000000", "aws.agent": "botocore", @@ -62,7 +62,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8ab00000000", "aws.agent": "botocore", @@ -117,7 +117,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8aa00000000", "aws.agent": "botocore", @@ -214,7 +214,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8a800000000", "aws.agent": "botocore", @@ -264,7 +264,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8a900000000", "aws.agent": "botocore", @@ -319,7 +319,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8a800000000", "aws.agent": "botocore", @@ -356,7 +356,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672ce8a800000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json index af0f6cc4ee0..444e9346443 100644 --- a/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json +++ b/tests/snapshots/tests.contrib.botocore.test.BotocoreTest.test_aws_payload_tagging_sqs.json @@ -9,7 +9,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672522b300000000", "aws.agent": "botocore", @@ -64,7 +64,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672522b200000000", "aws.agent": "botocore", @@ -101,7 +101,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672522b300000000", "aws.agent": "botocore", @@ -140,7 +140,7 @@ "type": "http", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "672522b300000000", "aws.agent": "botocore", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_ai21_invoke.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_ai21_invoke.json index 6ace8d2e279..c9e51d357fc 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_ai21_invoke.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_ai21_invoke.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c346700000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_embedding.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_embedding.json index f4c09d2734b..52fe47a79d8 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_embedding.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_embedding.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "662820e400000000", "bedrock.request.model": "titan-embed-text-v1", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke.json index 60bf72e8602..8a97aa3a5f6 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c3e1300000000", "bedrock.request.max_tokens": "50", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke_stream.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke_stream.json index d205669f6be..25c96fbfa8e 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke_stream.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_amazon_invoke_stream.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c3e1100000000", "bedrock.request.max_tokens": "50", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke.json index a379cc4ff7b..d611c648f01 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c342100000000", "bedrock.request.max_tokens": "50", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke_stream.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke_stream.json index 5d0854b4989..c833a69ac4a 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke_stream.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_invoke_stream.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c3d8c00000000", "bedrock.request.max_tokens": "50", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke.json index f0a629eeaed..1bbbe5d05a7 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6604ae4600000000", "bedrock.request.max_tokens": "", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke_stream.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke_stream.json index 12e06e4f93c..b1aed2025fb 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke_stream.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_anthropic_message_invoke_stream.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6604ae8300000000", "bedrock.request.max_tokens": "", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_auth_error.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_auth_error.json index b9cce34e661..aeaaf47d657 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_auth_error.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_auth_error.json @@ -9,7 +9,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659dea8c00000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_embedding.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_embedding.json index d1522b46ff5..f6deaba74ae 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_embedding.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_embedding.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "6628215a00000000", "bedrock.request.input_type": "search_document", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_multi_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_multi_output.json index 3f73a942d85..302c3b02957 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_multi_output.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_multi_output.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659d9e8200000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_single_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_single_output.json index 303b3353bd9..de967d47296 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_single_output.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_single_output.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c3d5c00000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multi_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multi_output.json index 4aec0662f05..506f32bb0ab 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multi_output.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multi_output.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "65a0359000000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json index 4aec0662f05..506f32bb0ab 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "65a0359000000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_single_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_single_output.json index 3a796e09b48..f6c889bcffb 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_single_output.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_single_output.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "65a0359100000000", "bedrock.request.max_tokens": "10", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke.json index e6c663215f7..ade15ee69a4 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659c394300000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke_stream.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke_stream.json index 0e41e2c94ca..c163928cb62 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke_stream.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_meta_invoke_stream.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659d9d8600000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_error.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_error.json index cbe895189c0..a99c84fa59f 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_error.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_error.json @@ -9,7 +9,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659dea8e00000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json index abdffb55cdf..6a506cd9e88 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json @@ -9,7 +9,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659dea8d00000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_readlines_error.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_readlines_error.json index 05f483c95ae..3090d3b72e6 100644 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_readlines_error.json +++ b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_readlines_error.json @@ -9,7 +9,7 @@ "type": "", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.botocore", "_dd.p.dm": "-0", "_dd.p.tid": "659dea8c00000000", "bedrock.request.max_tokens": "60", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_post_1_1.json index 885c5869d6e..ec616f20262 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_pre_1_1.json index 05cbf8c1c1a..013aa941688 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_with_rate_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_post_1_1.json index 624c659b51e..ba0bdf7baa8 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_pre_1_1.json index 7a5f5f6e906..ded846eec7d 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_analytics_without_rate_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_post_1_1.json index 0e2aaa7f5ac..8569f187f1c 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_pre_1_1.json index 1a3022a5a0a..f6153568f25 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_commit_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_post_1_1.json index 406044ff0ad..191f25e8312 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -46,7 +46,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -82,7 +82,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_pre_1_1.json index bf4b62211ce..8c27826fa30 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_many_fetchall_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -46,7 +46,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -82,7 +82,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_post_1_1.json index 6cc620e091e..559bed10ff8 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_pre_1_1.json index eac5e7fa88a..481b03a7dfa 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_proc_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_post_1_1.json index d6fae273f73..22d48261daf 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -45,7 +45,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_pre_1_1.json index 2fa91b17e28..6abf3a15163 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_fetchall_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -45,7 +45,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_post_1_1.json index 2c0575d6e71..f53f2de2bec 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_pre_1_1.json index fe92e969449..8b4399bd455 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_query_with_several_rows_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_post_1_1.json index c6ebb31cf4b..c02547ce492 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_pre_1_1.json index 8d01adbccce..1ee7c6b72f8 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_malformed_query_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_post_1_1.json index 0b017291cc2..241101d6696 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", @@ -45,7 +45,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_pre_1_1.json index 7cf907cee77..3d12c4ac067 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_fetchall_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "66b2668100000000", "component": "mariadb", @@ -45,7 +45,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "66b2668100000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_post_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_post_1_1.json index d0175982430..dd042fc97d9 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_post_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_post_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_pre_1_1.json b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_pre_1_1.json index 43b6c722e63..3e933ad03a5 100644 --- a/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_pre_1_1.json +++ b/tests/snapshots/tests.contrib.mariadb.test_mariadb.test_simple_query_snapshot_pre_1_1.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.mariadb", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "mariadb", diff --git a/tests/snapshots/tests.contrib.psycopg.test_psycopg.test_composed_query_encoding.json b/tests/snapshots/tests.contrib.psycopg.test_psycopg.test_composed_query_encoding.json index 6ac1335e360..c78bd880943 100644 --- a/tests/snapshots/tests.contrib.psycopg.test_psycopg.test_composed_query_encoding.json +++ b/tests/snapshots/tests.contrib.psycopg.test_psycopg.test_composed_query_encoding.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.psycopg", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "psycopg", diff --git a/tests/snapshots/tests.contrib.psycopg2.test_psycopg.test_composed_query_encoding.json b/tests/snapshots/tests.contrib.psycopg2.test_psycopg.test_composed_query_encoding.json index e9c716fce89..8a7c86337a0 100644 --- a/tests/snapshots/tests.contrib.psycopg2.test_psycopg.test_composed_query_encoding.json +++ b/tests/snapshots/tests.contrib.psycopg2.test_psycopg.test_composed_query_encoding.json @@ -9,7 +9,7 @@ "type": "sql", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.psycopg2", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "psycopg", diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_otel_ddtrace_mixed_parenting.json b/tests/snapshots/tests.opentelemetry.test_context.test_otel_ddtrace_mixed_parenting.json index 71f638187af..48326bd2cd7 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_otel_ddtrace_mixed_parenting.json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_otel_ddtrace_mixed_parenting.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-top-level", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "ddtrace-top-level", - "service": "", + "service": "tests.opentelemetry", "resource": "ddtrace-top-level", "trace_id": 0, "span_id": 2, @@ -37,7 +37,7 @@ }, { "name": "ddtrace-child", - "service": "", + "service": "tests.opentelemetry", "resource": "ddtrace-child", "trace_id": 0, "span_id": 3, @@ -49,7 +49,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-child", "trace_id": 0, "span_id": 4, @@ -61,7 +61,7 @@ }, { "name": "ddtrace-grandchild", - "service": "", + "service": "tests.opentelemetry", "resource": "ddtrace-grandchild", "trace_id": 0, "span_id": 5, @@ -73,7 +73,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-grandchild", "trace_id": 0, "span_id": 6, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_otel_multithreading.json b/tests/snapshots/tests.opentelemetry.test_context.test_otel_multithreading.json index 91fb173ee8b..b4ee89cd945 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_otel_multithreading.json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_otel_multithreading.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-threading-root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s1", "trace_id": 0, "span_id": 2, @@ -44,7 +44,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s2", "trace_id": 0, "span_id": 6, @@ -56,7 +56,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s3", "trace_id": 0, "span_id": 7, @@ -68,7 +68,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s1", "trace_id": 0, "span_id": 3, @@ -87,7 +87,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s2", "trace_id": 0, "span_id": 8, @@ -99,7 +99,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s3", "trace_id": 0, "span_id": 9, @@ -111,7 +111,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s1", "trace_id": 0, "span_id": 4, @@ -130,7 +130,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s2", "trace_id": 0, "span_id": 10, @@ -142,7 +142,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s3", "trace_id": 0, "span_id": 11, @@ -154,7 +154,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s1", "trace_id": 0, "span_id": 5, @@ -173,7 +173,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s2", "trace_id": 0, "span_id": 12, @@ -185,7 +185,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "s3", "trace_id": 0, "span_id": 13, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_otel_span_parenting.json b/tests/snapshots/tests.opentelemetry.test_context.test_otel_span_parenting.json index 203978ef0b0..89919325a5c 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_otel_span_parenting.json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_otel_span_parenting.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-parent1", "trace_id": 0, "span_id": 2, @@ -37,7 +37,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-child1", "trace_id": 0, "span_id": 5, @@ -49,7 +49,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "orphan1", "trace_id": 0, "span_id": 3, @@ -61,7 +61,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-parent2", "trace_id": 0, "span_id": 4, @@ -80,7 +80,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-child2", "trace_id": 0, "span_id": 6, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_across_fork.json b/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_across_fork.json index 2c85a9ade85..8278949e7f9 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_across_fork.json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_across_fork.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "task", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_multiple_coroutines.json b/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_multiple_coroutines.json index c06cb90984c..db6839d2905 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_multiple_coroutines.json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_otel_trace_multiple_coroutines.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "corountine 1", "trace_id": 0, "span_id": 2, @@ -37,7 +37,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "corountine 2", "trace_id": 0, "span_id": 3, @@ -49,7 +49,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "corountine 3", "trace_id": 0, "span_id": 4, @@ -61,7 +61,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "corountine 4", "trace_id": 0, "span_id": 5, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.drop].json b/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.drop].json index e55e40e6c40..ff14448cdbe 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.drop].json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.drop].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "task", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.keep].json b/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.keep].json index b54abdf3c8b..1f7458c01fa 100644 --- a/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.keep].json +++ b/tests/snapshots/tests.opentelemetry.test_context.test_sampling_decisions_across_processes[manual.keep].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "task", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json index 1bef77da56d..0fff2ecd8c9 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.opentelemetry", "_dd.p.dm": "-0", "_dd.p.tid": "655529ab00000000", "language": "python", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.opentelemetry", "_dd.p.dm": "-0", "_dd.p.tid": "655529ab00000000", "language": "python", @@ -63,7 +63,7 @@ [ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-list-tags", "trace_id": 2, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override0].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override0].json index 1cca5230415..9e4e9ab4f90 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override0].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override0].json @@ -1,7 +1,7 @@ [[ { "name": "operation-override", - "service": "", + "service": "tests.opentelemetry", "resource": "set-operation.name", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override1].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override1].json index b6cdc8393f8..f489dc10efe 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override1].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override1].json @@ -9,7 +9,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.opentelemetry", "_dd.p.dm": "-0", "_dd.p.tid": "655529ab00000000", "language": "python", diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override2].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override2].json index 0925e97b3fd..5e8f7e444e4 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override2].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override2].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "resource-override", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override3].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override3].json index bb75ec77be9..37c1857476e 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override3].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override3].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "set-span.type", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override4].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override4].json index dc554bff725..524a3f5f14b 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override4].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override4].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "set-analytics.event", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json index d346194b8e7..a19349c9dbc 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "set-http.response.status_code", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_events.json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_events.json index 760e67215b4..d66af11d70b 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_events.json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_events.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "webpage.load", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "web.response", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_kind.json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_kind.json index d8da2d8f76f..211acde5b16 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_kind.json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_kind.json @@ -1,7 +1,7 @@ [[ { "name": "client", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-client", "trace_id": 0, "span_id": 1, @@ -27,7 +27,7 @@ [ { "name": "server", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-server", "trace_id": 1, "span_id": 1, @@ -53,7 +53,7 @@ [ { "name": "producer", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-producer", "trace_id": 2, "span_id": 1, @@ -79,7 +79,7 @@ [ { "name": "consumer", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-consumer", "trace_id": 3, "span_id": 1, @@ -105,7 +105,7 @@ [ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-internal", "trace_id": 4, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_ddtrace_run].json b/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_ddtrace_run].json index dc4264a3675..e5188b56ba4 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_ddtrace_run].json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_ddtrace_run].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "test-otel-distributed-trace", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_opentelemetry_instrument].json b/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_opentelemetry_instrument].json index 786a7d0bd54..00d1b0b71dd 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_opentelemetry_instrument].json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_distributed_trace_with_flask_app[with_opentelemetry_instrument].json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "test-otel-distributed-trace", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_with_default_args.json b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_with_default_args.json index 70ed0df6ea6..b790e082ec4 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_with_default_args.json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_with_default_args.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "rename-start-current-span", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_without_default_args.json b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_without_default_args.json index 5ef41308b42..f50d27bdf29 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_without_default_args.json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_current_span_without_default_args.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root-span", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "server", - "service": "", + "service": "tests.opentelemetry", "resource": "rename-start-current-span", "trace_id": 0, "span_id": 2, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_ignore_exceptions.json b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_ignore_exceptions.json index d6e446ef7f6..a843ac04ac7 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_ignore_exceptions.json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_ignore_exceptions.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "otel-error-span", "trace_id": 0, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_record_exception.json b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_record_exception.json index 14003aaa787..a98e078f41f 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_record_exception.json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_record_exception.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "test-raised-exception", "trace_id": 0, "span_id": 1, @@ -30,7 +30,7 @@ [ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "test-recorded-exception", "trace_id": 1, "span_id": 1, diff --git a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_without_default_args.json b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_without_default_args.json index db6e10880f2..264b4357359 100644 --- a/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_without_default_args.json +++ b/tests/snapshots/tests.opentelemetry.test_trace.test_otel_start_span_without_default_args.json @@ -1,7 +1,7 @@ [[ { "name": "internal", - "service": "", + "service": "tests.opentelemetry", "resource": "root-span", "trace_id": 0, "span_id": 1, @@ -25,7 +25,7 @@ }, { "name": "client", - "service": "", + "service": "tests.opentelemetry", "resource": "rename-start-span", "trace_id": 0, "span_id": 2, From d45dd33067c4b3d8edf4c4879ba2f2ec40e20acd Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:39:05 -0800 Subject: [PATCH 191/372] chore(tests): turn off dogweb CI runs (#11414) This change disables dogfooding CI runs triggered from this repo, because they currently cost more than the benefit they provide. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab-ci.yml | 3 --- .gitlab/dogfood.yml | 18 ------------------ 2 files changed, 21 deletions(-) delete mode 100644 .gitlab/dogfood.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e5ee6e5a2b4..b29529e6106 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,10 +3,8 @@ stages: - tests-gen - tests-trigger - shared-pipeline - - dogfood - benchmarks - macrobenchmarks - - dogfood - release variables: @@ -22,7 +20,6 @@ include: - local: ".gitlab/benchmarks.yml" - local: ".gitlab/package.yml" - local: ".gitlab/macrobenchmarks.yml" - - local: ".gitlab/dogfood.yml" - local: ".gitlab/release.yml" - local: ".gitlab/testrunner.yml" diff --git a/.gitlab/dogfood.yml b/.gitlab/dogfood.yml deleted file mode 100644 index 4664881c05b..00000000000 --- a/.gitlab/dogfood.yml +++ /dev/null @@ -1,18 +0,0 @@ -dogfood-dogweb-trigger: - stage: dogfood - trigger: - project: DataDog/dogweb - branch: emmett.butler/ddtrace-unstable-dogfooding - allow_failure: true - needs: [] - variables: - UPSTREAM_PIPELINE_ID: $CI_PIPELINE_ID - UPSTREAM_PROJECT_URL: $CI_PROJECT_URL - DDTRACE_COMMIT_TO_INSTALL: $CI_COMMIT_SHA - UPSTREAM_COMMIT_BRANCH: $CI_COMMIT_BRANCH - UPSTREAM_COMMIT_AUTHOR: $CI_COMMIT_AUTHOR - UPSTREAM_COMMIT_TITLE: $CI_COMMIT_TITLE - UPSTREAM_COMMIT_TAG: $CI_COMMIT_TAG - UPSTREAM_PROJECT_NAME: $CI_PROJECT_NAME - UPSTREAM_GITLAB_USER_LOGIN: $GITLAB_USER_LOGIN - UPSTREAM_GITLAB_USER_EMAIL: $GITLAB_USER_EMAIL From 7e1bb89a82198f4702553e9f133f188c4a144bf0 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Wed, 20 Nov 2024 14:06:37 -0500 Subject: [PATCH 192/372] chore: update changelog for version 2.16.4 (#11463) - [x] update changelog for version 2.16.4 --------- Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9414d839d20..e52b18d1ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.16.4 + + +### Bug Fixes + +- Tracing + - botocore: Resolves the issue where the span pointer for deserialized DynamoDB requests (through the resource-based API) were not being generated. + - botocore: Resolves an issue where our span pointer calculation code added recently logged unactionable messages. + + --- ## 2.16.2 From 78d35a2db6a74d208e00260795ccf95f24034c39 Mon Sep 17 00:00:00 2001 From: wantsui Date: Wed, 20 Nov 2024 14:22:25 -0500 Subject: [PATCH 193/372] chore: extend pyramid flaky test timestamp (#11369) The pyramid test flaky marker was set to Jan 2024 so it is expired by now. This PR updates the test. While looking at the failed pipelines, example: https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/703862626 One of the reasons it fails is because there's a new base service name and it is no longer blank: ``` meta mismatch on '_dd.base_service': got 'tests.contrib.pyramid.app' which does not match expected ''. ``` Related to https://github.com/DataDog/dd-trace-py/pull/11274 Also, this PR sets a new flaky timestamp because the pyramid sample app doesn't spin up correctly in the subprocesses, leading to failed tests. Relates to AIDM-483 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/pyramid/test_pyramid.py | 2 +- ...nt[ddtrace-run_python_tests_contrib_pyramid_app_app.py].json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/contrib/pyramid/test_pyramid.py b/tests/contrib/pyramid/test_pyramid.py index f036437b812..3cc9b2688ca 100644 --- a/tests/contrib/pyramid/test_pyramid.py +++ b/tests/contrib/pyramid/test_pyramid.py @@ -253,7 +253,7 @@ def pyramid_client(snapshot, pyramid_app): proc.terminate() -@flaky(until=1706677200) +@flaky(1740089353, reason="Sample app doesn't seem to spin up in subprocess") @pytest.mark.parametrize( "pyramid_app", [ diff --git a/tests/snapshots/tests.contrib.pyramid.test_pyramid.test_simple_pyramid_app_endpoint[ddtrace-run_python_tests_contrib_pyramid_app_app.py].json b/tests/snapshots/tests.contrib.pyramid.test_pyramid.test_simple_pyramid_app_endpoint[ddtrace-run_python_tests_contrib_pyramid_app_app.py].json index 6072dbb7254..a09bf7f0f27 100644 --- a/tests/snapshots/tests.contrib.pyramid.test_pyramid.test_simple_pyramid_app_endpoint[ddtrace-run_python_tests_contrib_pyramid_app_app.py].json +++ b/tests/snapshots/tests.contrib.pyramid.test_pyramid.test_simple_pyramid_app_endpoint[ddtrace-run_python_tests_contrib_pyramid_app_app.py].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.pyramid.app", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "pyramid", From 383f836b84e55b51fab9b6ed776632af845ea84b Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:35:48 -0800 Subject: [PATCH 194/372] fix: accept default versions of common packages [INPLAT-220] (#11464) This change lowers the minimum supported version of the `click` and `jinja2` packages to include the default versions installed under Python 3.8. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{100c918.txt => 1001365.txt} | 12 ++--- .riot/requirements/112dc22.txt | 6 +-- .../requirements/{4334c5c.txt => 121cdba.txt} | 38 ++++++++-------- .riot/requirements/1250d61.txt | 2 +- .../requirements/{1b3fcdf.txt => 130c755.txt} | 14 +++--- .../requirements/{8fc3285.txt => 1416d2d.txt} | 43 +++++++++--------- .../requirements/{118d2ec.txt => 142556c.txt} | 22 ++++----- .riot/requirements/156e3cc.txt | 10 ++--- .../requirements/{14a42dd.txt => 160ef98.txt} | 6 +-- .riot/requirements/17a0f7f.txt | 10 ++--- .riot/requirements/18392ae.txt | 14 +++--- .../requirements/{169c991.txt => 1a6b1f3.txt} | 45 +++++++++---------- .riot/requirements/1b846e9.txt | 6 +-- .riot/requirements/1c489e9.txt | 2 +- .riot/requirements/1d23fbc.txt | 25 +++++++++++ .../requirements/{b9941cb.txt => 1d9958f.txt} | 45 +++++++++---------- .riot/requirements/1e87e36.txt | 25 +++++++++++ .../requirements/{abfbfa5.txt => 1ec5ec9.txt} | 43 +++++++++--------- .../requirements/{1ac55e4.txt => 1f6c245.txt} | 45 +++++++++---------- .riot/requirements/1ff1ba2.txt | 25 ----------- .../requirements/{1cd1d4a.txt => 2239ee7.txt} | 45 +++++++++---------- .riot/requirements/2c855a9.txt | 14 +++--- .riot/requirements/41529f2.txt | 12 ++--- .../requirements/{1a6e474.txt => 43a4702.txt} | 38 ++++++++-------- .../requirements/{9b18162.txt => 4ad11b1.txt} | 14 +++--- .riot/requirements/4b1629e.txt | 12 ++--- .riot/requirements/7b8e50e.txt | 6 +-- .riot/requirements/7f7863d.txt | 14 +++--- .riot/requirements/7ff8c97.txt | 12 ++--- .../requirements/{f0bc737.txt => 8854702.txt} | 14 +++--- .../requirements/{125f4b9.txt => 8b98948.txt} | 38 ++++++++-------- .riot/requirements/91a3315.txt | 2 +- .../requirements/{cea6065.txt => a7df384.txt} | 45 +++++++++---------- .../requirements/{a4ee05c.txt => af02264.txt} | 20 ++++----- .riot/requirements/b06b6cb.txt | 12 ++--- .riot/requirements/c52f9f6.txt | 8 ++-- .riot/requirements/d6ceb22.txt | 12 ++--- .../requirements/{1853bc5.txt => d785d62.txt} | 45 +++++++++---------- .riot/requirements/d88b3ac.txt | 14 +++--- .riot/requirements/de13093.txt | 25 ----------- .riot/requirements/ec338d4.txt | 16 +++---- .../requirements/{3f38536.txt => f0a4d86.txt} | 43 +++++++++--------- .../sources/min_compatible_versions.csv | 19 ++++---- min_compatible_versions.csv | 19 ++++---- .../notes/lower-minima-8c969f5a71402e01.yaml | 5 +++ riotfile.py | 19 ++++---- scripts/min_compatible_versions.py | 2 +- 47 files changed, 481 insertions(+), 482 deletions(-) rename .riot/requirements/{100c918.txt => 1001365.txt} (77%) rename .riot/requirements/{4334c5c.txt => 121cdba.txt} (71%) rename .riot/requirements/{1b3fcdf.txt => 130c755.txt} (84%) rename .riot/requirements/{8fc3285.txt => 1416d2d.txt} (65%) rename .riot/requirements/{118d2ec.txt => 142556c.txt} (63%) rename .riot/requirements/{14a42dd.txt => 160ef98.txt} (81%) rename .riot/requirements/{169c991.txt => 1a6b1f3.txt} (65%) create mode 100644 .riot/requirements/1d23fbc.txt rename .riot/requirements/{b9941cb.txt => 1d9958f.txt} (65%) create mode 100644 .riot/requirements/1e87e36.txt rename .riot/requirements/{abfbfa5.txt => 1ec5ec9.txt} (65%) rename .riot/requirements/{1ac55e4.txt => 1f6c245.txt} (65%) delete mode 100644 .riot/requirements/1ff1ba2.txt rename .riot/requirements/{1cd1d4a.txt => 2239ee7.txt} (65%) rename .riot/requirements/{1a6e474.txt => 43a4702.txt} (71%) rename .riot/requirements/{9b18162.txt => 4ad11b1.txt} (84%) rename .riot/requirements/{f0bc737.txt => 8854702.txt} (84%) rename .riot/requirements/{125f4b9.txt => 8b98948.txt} (71%) rename .riot/requirements/{cea6065.txt => a7df384.txt} (65%) rename .riot/requirements/{a4ee05c.txt => af02264.txt} (66%) rename .riot/requirements/{1853bc5.txt => d785d62.txt} (65%) delete mode 100644 .riot/requirements/de13093.txt rename .riot/requirements/{3f38536.txt => f0a4d86.txt} (65%) create mode 100644 releasenotes/notes/lower-minima-8c969f5a71402e01.yaml diff --git a/.riot/requirements/100c918.txt b/.riot/requirements/1001365.txt similarity index 77% rename from .riot/requirements/100c918.txt rename to .riot/requirements/1001365.txt index 1851ed8bad5..4c93e023297 100644 --- a/.riot/requirements/100c918.txt +++ b/.riot/requirements/1001365.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/100c918.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1001365.in # -attrs==23.1.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 markupsafe==1.1.1 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 -pytest==7.4.3 +pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/112dc22.txt b/.riot/requirements/112dc22.txt index 66f398229a6..5a7a17b01c7 100644 --- a/.riot/requirements/112dc22.txt +++ b/.riot/requirements/112dc22.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.14.2 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/4334c5c.txt b/.riot/requirements/121cdba.txt similarity index 71% rename from .riot/requirements/4334c5c.txt rename to .riot/requirements/121cdba.txt index 357671c41d1..9d30c267b23 100644 --- a/.riot/requirements/4334c5c.txt +++ b/.riot/requirements/121cdba.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/4334c5c.in +# pip-compile --no-annotate .riot/requirements/121cdba.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 -cryptography==43.0.1 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,36 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.20.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/1250d61.txt b/.riot/requirements/1250d61.txt index 2fc3bd39576..90fc52cde99 100644 --- a/.riot/requirements/1250d61.txt +++ b/.riot/requirements/1250d61.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/1b3fcdf.txt b/.riot/requirements/130c755.txt similarity index 84% rename from .riot/requirements/1b3fcdf.txt rename to .riot/requirements/130c755.txt index 690865da4a8..4ff3f89fa5e 100644 --- a/.riot/requirements/1b3fcdf.txt +++ b/.riot/requirements/130c755.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1b3fcdf.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/130c755.in # annotated-types==0.5.0 attrs==24.2.0 @@ -14,9 +14,9 @@ botocore==1.33.13 certifi==2024.8.30 cffi==1.15.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 -cryptography==43.0.1 +cryptography==43.0.3 docker==6.1.3 ecdsa==0.14.1 exceptiongroup==1.2.2 @@ -24,7 +24,7 @@ hypothesis==6.45.0 idna==2.10 importlib-metadata==6.7.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.0.0 jsonpatch==1.33 @@ -51,7 +51,7 @@ pytest-mock==3.11.1 pytest-randomly==3.12.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.1 requests==2.31.0 responses==0.23.3 @@ -67,8 +67,8 @@ urllib3==1.26.20 websocket-client==1.6.1 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 +xmltodict==0.14.2 zipp==3.15.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==68.0.0 +# setuptools diff --git a/.riot/requirements/8fc3285.txt b/.riot/requirements/1416d2d.txt similarity index 65% rename from .riot/requirements/8fc3285.txt rename to .riot/requirements/1416d2d.txt index 70931ed9cd2..71acdb59ce2 100644 --- a/.riot/requirements/8fc3285.txt +++ b/.riot/requirements/1416d2d.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/8fc3285.in +# pip-compile --no-annotate .riot/requirements/1416d2d.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -35,36 +35,35 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/118d2ec.txt b/.riot/requirements/142556c.txt similarity index 63% rename from .riot/requirements/118d2ec.txt rename to .riot/requirements/142556c.txt index cceeac15f45..8d2b90a5faa 100644 --- a/.riot/requirements/118d2ec.txt +++ b/.riot/requirements/142556c.txt @@ -2,36 +2,36 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/118d2ec.in +# pip-compile --no-annotate .riot/requirements/142556c.in # atomicwrites==1.4.1 -attrs==23.2.0 -blinker==1.7.0 +attrs==24.2.0 +blinker==1.9.0 click==8.1.7 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 +coverage[toml]==7.6.7 +exceptiongroup==1.2.2 flask==0.12.5 flask-cache==0.13.1 hypothesis==6.45.0 -importlib-metadata==7.1.0 +importlib-metadata==8.5.0 itsdangerous==1.1.0 -jinja2==2.11.3 +jinja2==2.10.3 markupsafe==1.1.1 mock==5.1.0 more-itertools==8.10.0 opentracing==2.4.0 -packaging==24.0 +packaging==24.2 pluggy==0.13.1 py==1.11.0 pytest==4.6.11 pytest-cov==3.0.0 pytest-mock==2.0.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-memcached==1.62 redis==2.10.6 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 wcwidth==0.2.13 werkzeug==0.16.1 -zipp==3.18.1 +zipp==3.21.0 diff --git a/.riot/requirements/156e3cc.txt b/.riot/requirements/156e3cc.txt index 0ab8b16d0de..1525da6c414 100644 --- a/.riot/requirements/156e3cc.txt +++ b/.riot/requirements/156e3cc.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,13 +21,13 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/14a42dd.txt b/.riot/requirements/160ef98.txt similarity index 81% rename from .riot/requirements/14a42dd.txt rename to .riot/requirements/160ef98.txt index 8070a820d63..daea0acc6d0 100644 --- a/.riot/requirements/14a42dd.txt +++ b/.riot/requirements/160ef98.txt @@ -2,10 +2,10 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/14a42dd.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/160ef98.in # atomicwrites==1.4.1 -attrs==23.2.0 +attrs==24.2.0 blinker==1.6.3 click==8.1.7 coverage[toml]==7.2.7 @@ -14,7 +14,7 @@ flask-cache==0.13.1 hypothesis==6.45.0 importlib-metadata==6.7.0 itsdangerous==1.1.0 -jinja2==2.11.3 +jinja2==2.10.3 markupsafe==1.1.1 mock==5.1.0 more-itertools==8.10.0 diff --git a/.riot/requirements/17a0f7f.txt b/.riot/requirements/17a0f7f.txt index b4b0a7c9503..d6725f817cd 100644 --- a/.riot/requirements/17a0f7f.txt +++ b/.riot/requirements/17a0f7f.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,13 +21,13 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/18392ae.txt b/.riot/requirements/18392ae.txt index fb77230f5a3..933de189d61 100644 --- a/.riot/requirements/18392ae.txt +++ b/.riot/requirements/18392ae.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.41.2 -tomli==2.1.0 +starlette==0.39.2 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/169c991.txt b/.riot/requirements/1a6b1f3.txt similarity index 65% rename from .riot/requirements/169c991.txt rename to .riot/requirements/1a6b1f3.txt index 1f3c8c41195..dd3d3d301ee 100644 --- a/.riot/requirements/169c991.txt +++ b/.riot/requirements/1a6b1f3.txt @@ -2,28 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/169c991.in +# pip-compile --no-annotate .riot/requirements/1a6b1f3.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -36,37 +36,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/1b846e9.txt b/.riot/requirements/1b846e9.txt index 378008e2448..4d174ec147c 100644 --- a/.riot/requirements/1b846e9.txt +++ b/.riot/requirements/1b846e9.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/1c489e9.txt b/.riot/requirements/1c489e9.txt index 01e263f1e3a..92254158db9 100644 --- a/.riot/requirements/1c489e9.txt +++ b/.riot/requirements/1c489e9.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/1d23fbc.txt b/.riot/requirements/1d23fbc.txt new file mode 100644 index 00000000000..ba4db809a86 --- /dev/null +++ b/.riot/requirements/1d23fbc.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1d23fbc.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +jinja2==2.10.3 +markupsafe==1.1.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +zipp==3.20.2 diff --git a/.riot/requirements/b9941cb.txt b/.riot/requirements/1d9958f.txt similarity index 65% rename from .riot/requirements/b9941cb.txt rename to .riot/requirements/1d9958f.txt index e0fc21c59a3..c7f6fdbe310 100644 --- a/.riot/requirements/b9941cb.txt +++ b/.riot/requirements/1d9958f.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/b9941cb.in +# pip-compile --no-annotate .riot/requirements/1d9958f.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,37 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/1e87e36.txt b/.riot/requirements/1e87e36.txt new file mode 100644 index 00000000000..bbf6cfe5d53 --- /dev/null +++ b/.riot/requirements/1e87e36.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1e87e36.in +# +attrs==24.2.0 +coverage[toml]==7.6.7 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +jinja2==2.10.3 +markupsafe==1.1.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/abfbfa5.txt b/.riot/requirements/1ec5ec9.txt similarity index 65% rename from .riot/requirements/abfbfa5.txt rename to .riot/requirements/1ec5ec9.txt index 76102ef1cea..9a2af6eeca1 100644 --- a/.riot/requirements/abfbfa5.txt +++ b/.riot/requirements/1ec5ec9.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/abfbfa5.in +# pip-compile --no-annotate .riot/requirements/1ec5ec9.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -35,36 +35,35 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/1ac55e4.txt b/.riot/requirements/1f6c245.txt similarity index 65% rename from .riot/requirements/1ac55e4.txt rename to .riot/requirements/1f6c245.txt index f2d3c811936..0615f6c278b 100644 --- a/.riot/requirements/1ac55e4.txt +++ b/.riot/requirements/1f6c245.txt @@ -2,28 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ac55e4.in +# pip-compile --no-annotate .riot/requirements/1f6c245.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -36,37 +36,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/1ff1ba2.txt b/.riot/requirements/1ff1ba2.txt deleted file mode 100644 index 36f7d263d98..00000000000 --- a/.riot/requirements/1ff1ba2.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ff1ba2.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1cd1d4a.txt b/.riot/requirements/2239ee7.txt similarity index 65% rename from .riot/requirements/1cd1d4a.txt rename to .riot/requirements/2239ee7.txt index e4c75077843..c0f1fb94496 100644 --- a/.riot/requirements/1cd1d4a.txt +++ b/.riot/requirements/2239ee7.txt @@ -2,28 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1cd1d4a.in +# pip-compile --no-annotate .riot/requirements/2239ee7.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -36,37 +36,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/2c855a9.txt b/.riot/requirements/2c855a9.txt index 33e8f8f4af3..0145630e743 100644 --- a/.riot/requirements/2c855a9.txt +++ b/.riot/requirements/2c855a9.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.14.2 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.21.0 +zipp==3.20.2 diff --git a/.riot/requirements/41529f2.txt b/.riot/requirements/41529f2.txt index 13d7043047a..83a605e2881 100644 --- a/.riot/requirements/41529f2.txt +++ b/.riot/requirements/41529f2.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,17 +21,17 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.41.2 +starlette==0.39.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1a6e474.txt b/.riot/requirements/43a4702.txt similarity index 71% rename from .riot/requirements/1a6e474.txt rename to .riot/requirements/43a4702.txt index 19ec6ca5b2e..401f6878906 100644 --- a/.riot/requirements/1a6e474.txt +++ b/.riot/requirements/43a4702.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a6e474.in +# pip-compile --no-annotate .riot/requirements/43a4702.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 -cryptography==43.0.1 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,36 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.20.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/9b18162.txt b/.riot/requirements/4ad11b1.txt similarity index 84% rename from .riot/requirements/9b18162.txt rename to .riot/requirements/4ad11b1.txt index d35e7d0a9b1..ee4502f597b 100644 --- a/.riot/requirements/9b18162.txt +++ b/.riot/requirements/4ad11b1.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/9b18162.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/4ad11b1.in # annotated-types==0.5.0 attrs==24.2.0 @@ -14,9 +14,9 @@ botocore==1.33.13 certifi==2024.8.30 cffi==1.15.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 -cryptography==43.0.1 +cryptography==43.0.3 docker==6.1.3 ecdsa==0.14.1 exceptiongroup==1.2.2 @@ -24,7 +24,7 @@ hypothesis==6.45.0 idna==2.10 importlib-metadata==6.7.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.0.0 jsonpatch==1.33 @@ -51,7 +51,7 @@ pytest-mock==3.11.1 pytest-randomly==3.12.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.1 requests==2.31.0 responses==0.23.3 @@ -67,8 +67,8 @@ urllib3==1.26.20 websocket-client==1.6.1 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 +xmltodict==0.14.2 zipp==3.15.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==68.0.0 +# setuptools diff --git a/.riot/requirements/4b1629e.txt b/.riot/requirements/4b1629e.txt index b5b3335484e..76be9c2a6d2 100644 --- a/.riot/requirements/4b1629e.txt +++ b/.riot/requirements/4b1629e.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/7b8e50e.txt b/.riot/requirements/7b8e50e.txt index 310164f1e0d..cd7fc9062b9 100644 --- a/.riot/requirements/7b8e50e.txt +++ b/.riot/requirements/7b8e50e.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -35,7 +35,7 @@ sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/7f7863d.txt b/.riot/requirements/7f7863d.txt index 598f660a429..361a3483306 100644 --- a/.riot/requirements/7f7863d.txt +++ b/.riot/requirements/7f7863d.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.20.4 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.21.0 +zipp==3.20.2 diff --git a/.riot/requirements/7ff8c97.txt b/.riot/requirements/7ff8c97.txt index 1ffe5129825..ab3925fdd61 100644 --- a/.riot/requirements/7ff8c97.txt +++ b/.riot/requirements/7ff8c97.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/f0bc737.txt b/.riot/requirements/8854702.txt similarity index 84% rename from .riot/requirements/f0bc737.txt rename to .riot/requirements/8854702.txt index e514da947a1..dfbd9884664 100644 --- a/.riot/requirements/f0bc737.txt +++ b/.riot/requirements/8854702.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/f0bc737.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/8854702.in # annotated-types==0.5.0 attrs==24.2.0 @@ -14,9 +14,9 @@ botocore==1.33.13 certifi==2024.8.30 cffi==1.15.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.2.7 -cryptography==43.0.1 +cryptography==43.0.3 docker==6.1.3 ecdsa==0.14.1 exceptiongroup==1.2.2 @@ -24,7 +24,7 @@ hypothesis==6.45.0 idna==2.10 importlib-metadata==6.7.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.0.0 jsonpatch==1.33 @@ -51,7 +51,7 @@ pytest-mock==3.11.1 pytest-randomly==3.12.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.1 requests==2.31.0 responses==0.23.3 @@ -67,8 +67,8 @@ urllib3==1.26.20 websocket-client==1.6.1 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 +xmltodict==0.14.2 zipp==3.15.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==68.0.0 +# setuptools diff --git a/.riot/requirements/125f4b9.txt b/.riot/requirements/8b98948.txt similarity index 71% rename from .riot/requirements/125f4b9.txt rename to .riot/requirements/8b98948.txt index 3492ca38331..f22e6f8ab46 100644 --- a/.riot/requirements/125f4b9.txt +++ b/.riot/requirements/8b98948.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/125f4b9.in +# pip-compile --no-annotate .riot/requirements/8b98948.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 coverage[toml]==7.6.1 -cryptography==43.0.1 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,36 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 +pytest==8.3.3 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.20.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/91a3315.txt b/.riot/requirements/91a3315.txt index 2f05255dc5d..59c24169550 100644 --- a/.riot/requirements/91a3315.txt +++ b/.riot/requirements/91a3315.txt @@ -9,7 +9,7 @@ aiosqlite==0.19.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.2.7 databases==0.8.0 exceptiongroup==1.2.2 diff --git a/.riot/requirements/cea6065.txt b/.riot/requirements/a7df384.txt similarity index 65% rename from .riot/requirements/cea6065.txt rename to .riot/requirements/a7df384.txt index 83fb400c3ba..8aa1055eb0e 100644 --- a/.riot/requirements/cea6065.txt +++ b/.riot/requirements/a7df384.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/cea6065.in +# pip-compile --no-annotate .riot/requirements/a7df384.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,37 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/a4ee05c.txt b/.riot/requirements/af02264.txt similarity index 66% rename from .riot/requirements/a4ee05c.txt rename to .riot/requirements/af02264.txt index e88e88b7770..34794a907ae 100644 --- a/.riot/requirements/a4ee05c.txt +++ b/.riot/requirements/af02264.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/a4ee05c.in +# pip-compile --no-annotate .riot/requirements/af02264.in # atomicwrites==1.4.1 -attrs==23.2.0 -blinker==1.7.0 +attrs==24.2.0 +blinker==1.8.2 click==8.1.7 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 flask==0.12.5 flask-cache==0.13.1 hypothesis==6.45.0 -importlib-metadata==7.1.0 +importlib-metadata==8.5.0 itsdangerous==1.1.0 -jinja2==2.11.3 +jinja2==2.10.3 markupsafe==1.1.1 mock==5.1.0 more-itertools==8.10.0 opentracing==2.4.0 -packaging==24.0 +packaging==24.2 pluggy==0.13.1 py==1.11.0 pytest==4.6.11 @@ -31,7 +31,7 @@ python-memcached==1.62 redis==2.10.6 six==1.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.1.0 wcwidth==0.2.13 werkzeug==0.16.1 -zipp==3.18.1 +zipp==3.20.2 diff --git a/.riot/requirements/b06b6cb.txt b/.riot/requirements/b06b6cb.txt index e277fb20f5a..f74a362923f 100644 --- a/.riot/requirements/b06b6cb.txt +++ b/.riot/requirements/b06b6cb.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -22,18 +22,18 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.15.0 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/c52f9f6.txt b/.riot/requirements/c52f9f6.txt index 6b62e916274..c65ef57f787 100644 --- a/.riot/requirements/c52f9f6.txt +++ b/.riot/requirements/c52f9f6.txt @@ -9,7 +9,7 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 +charset-normalizer==3.3.2 coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 @@ -34,8 +34,8 @@ requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.41.2 -tomli==2.1.0 +starlette==0.39.2 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/d6ceb22.txt b/.riot/requirements/d6ceb22.txt index 6ee8e73017d..0749513724c 100644 --- a/.riot/requirements/d6ceb22.txt +++ b/.riot/requirements/d6ceb22.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 greenlet==3.0.3 h11==0.14.0 @@ -21,17 +21,17 @@ idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.41.2 +starlette==0.39.2 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1853bc5.txt b/.riot/requirements/d785d62.txt similarity index 65% rename from .riot/requirements/1853bc5.txt rename to .riot/requirements/d785d62.txt index 16be4a95906..b3a4e3b51d7 100644 --- a/.riot/requirements/1853bc5.txt +++ b/.riot/requirements/d785d62.txt @@ -2,29 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1853bc5.in +# pip-compile --no-annotate .riot/requirements/d785d62.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 exceptiongroup==1.2.2 hypothesis==6.45.0 idna==2.10 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -37,37 +37,36 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 -tomli==2.0.1 +tomli==2.1.0 typing-extensions==4.12.2 -tzdata==2024.1 urllib3==1.26.20 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/.riot/requirements/d88b3ac.txt b/.riot/requirements/d88b3ac.txt index 785f5b75b11..b1cbde620f2 100644 --- a/.riot/requirements/d88b3ac.txt +++ b/.riot/requirements/d88b3ac.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 starlette==0.33.0 -tomli==2.1.0 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.21.0 +zipp==3.20.2 diff --git a/.riot/requirements/de13093.txt b/.riot/requirements/de13093.txt deleted file mode 100644 index d9d407a138a..00000000000 --- a/.riot/requirements/de13093.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/de13093.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/ec338d4.txt b/.riot/requirements/ec338d4.txt index 2c823fe1300..e5a0a713b20 100644 --- a/.riot/requirements/ec338d4.txt +++ b/.riot/requirements/ec338d4.txt @@ -9,8 +9,8 @@ aiosqlite==0.20.0 anyio==3.7.1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.4 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 databases==0.8.0 exceptiongroup==1.2.2 greenlet==3.0.3 @@ -23,19 +23,19 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.2 +packaging==24.1 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.21.1 -pytest-cov==6.0.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-randomly==3.16.0 +pytest-randomly==3.15.0 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 sqlalchemy==1.4.54 -starlette==0.41.2 -tomli==2.1.0 +starlette==0.39.2 +tomli==2.0.2 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.21.0 +zipp==3.20.2 diff --git a/.riot/requirements/3f38536.txt b/.riot/requirements/f0a4d86.txt similarity index 65% rename from .riot/requirements/3f38536.txt rename to .riot/requirements/f0a4d86.txt index fe74d80b0cc..30ff4ac30eb 100644 --- a/.riot/requirements/3f38536.txt +++ b/.riot/requirements/f0a4d86.txt @@ -2,27 +2,27 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3f38536.in +# pip-compile --no-annotate .riot/requirements/f0a4d86.in # annotated-types==0.7.0 attrs==24.2.0 -aws-sam-translator==1.91.0 +aws-sam-translator==1.92.0 aws-xray-sdk==2.14.0 boto==2.49.0 -boto3==1.35.14 -botocore==1.35.14 +boto3==1.35.65 +botocore==1.35.65 certifi==2024.8.30 cffi==1.17.1 cfn-lint==0.53.1 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -cryptography==43.0.1 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +cryptography==43.0.3 docker==7.1.0 ecdsa==0.14.1 hypothesis==6.45.0 idna==2.10 iniconfig==2.0.0 -jinja2==2.11.3 +jinja2==2.10.3 jmespath==1.0.1 jsondiff==2.2.1 jsonpatch==1.33 @@ -35,36 +35,35 @@ more-itertools==10.5.0 moto==1.3.16 networkx==2.8.8 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyasn1==0.6.0 +pyasn1==0.6.1 pycparser==2.22 -pydantic==2.9.0 -pydantic-core==2.23.2 +pydantic==2.9.2 +pydantic-core==2.23.4 pynamodb==5.5.1 pyrsistent==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.3 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-jose[cryptography]==3.3.0 -pytz==2024.1 +pytz==2024.2 pyyaml==6.0.2 requests==2.32.3 responses==0.25.3 rsa==4.9 -s3transfer==0.10.2 +s3transfer==0.10.3 six==1.16.0 sortedcontainers==2.4.0 sshpubkeys==3.3.1 typing-extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +urllib3==2.2.3 werkzeug==2.1.2 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.20.1 +xmltodict==0.14.2 +zipp==3.21.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.1.2 +# setuptools diff --git a/lib-injection/sources/min_compatible_versions.csv b/lib-injection/sources/min_compatible_versions.csv index 0928fd308be..b49ced9c42e 100644 --- a/lib-injection/sources/min_compatible_versions.csv +++ b/lib-injection/sources/min_compatible_versions.csv @@ -1,7 +1,7 @@ This file was generated by scripts/min_compatible_versions.py pkg_name,min_version Flask-Cache,~=0.13.1 -Jinja2,~=2.11.0 +Jinja2,~=2.10.0 SQLAlchemy,==2.0.22 WebTest,0 Werkzeug,<1.0 @@ -17,10 +17,10 @@ algoliasearch,~=2.5 anthropic,==0.26.0 anyio,>=3.4.0 aredis,0 -asgiref,~=3.0.0 +asgiref,~=3.0 astunparse,0 async_generator,~=1.10 -asyncpg,~=0.23.0 +asyncpg,~=0.23 asynctest,==0.13.0 austin-python,~=1.0 avro,0 @@ -34,7 +34,6 @@ celery,~=5.1.0 cfn-lint,~=0.53.1 channels,~=3.0 cherrypy,>=17 -click,==7.1.2 cohere,==4.57 confluent-kafka,~=1.9.2 coverage,0 @@ -63,18 +62,18 @@ elasticsearch[async],0 envier,==0.5.2 exceptiongroup,0 faiss-cpu,==1.8.0 -falcon,~=3.0.0 +falcon,~=3.0 fastapi,~=0.64.0 flask,~=0.12.0 flask-caching,~=1.10.0 flask-openapi3,0 gevent,~=20.12.0 -git+https://github.com/gnufede/pytest-memray.git@24a3c0735db99eedf57fb36c573680f9bab7cd73,0 +google-generativeai,0 googleapis-common-protos,0 graphene,~=3.0.0 graphql-core,~=3.2.0 graphql-relay,0 -greenlet,~=1.0.0 +greenlet,~=1.0 grpcio,~=1.34.0 gunicorn,==20.0.4 gunicorn[gevent],0 @@ -85,7 +84,7 @@ hypothesis,<6.45.1 importlib-metadata,0 importlib_metadata,<5.0 itsdangerous,<2.0 -jinja2,~=2.11.0 +jinja2,~=2.10.0 kombu,>=4.2.0 langchain,==0.0.192 langchain-anthropic,==0.1.11 @@ -144,12 +143,16 @@ pytest-aiohttp,0 pytest-bdd,>=4.0 pytest-benchmark,>=3.1.0 pytest-cov,==2.9.0 +pytest-cpp,0 pytest-django[testing],==3.10.0 +pytest-memray,~=1.7.0 pytest-mock,==2.0.0 pytest-sanic,~=1.6.2 python-consul,>=1.1 python-json-logger,==2.0.7 python-memcached,0 +python-multipart,0 +ragas,==0.1.21 redis,~=2.0 redis-py-cluster,>=2.0 reno,0 diff --git a/min_compatible_versions.csv b/min_compatible_versions.csv index 0928fd308be..b49ced9c42e 100644 --- a/min_compatible_versions.csv +++ b/min_compatible_versions.csv @@ -1,7 +1,7 @@ This file was generated by scripts/min_compatible_versions.py pkg_name,min_version Flask-Cache,~=0.13.1 -Jinja2,~=2.11.0 +Jinja2,~=2.10.0 SQLAlchemy,==2.0.22 WebTest,0 Werkzeug,<1.0 @@ -17,10 +17,10 @@ algoliasearch,~=2.5 anthropic,==0.26.0 anyio,>=3.4.0 aredis,0 -asgiref,~=3.0.0 +asgiref,~=3.0 astunparse,0 async_generator,~=1.10 -asyncpg,~=0.23.0 +asyncpg,~=0.23 asynctest,==0.13.0 austin-python,~=1.0 avro,0 @@ -34,7 +34,6 @@ celery,~=5.1.0 cfn-lint,~=0.53.1 channels,~=3.0 cherrypy,>=17 -click,==7.1.2 cohere,==4.57 confluent-kafka,~=1.9.2 coverage,0 @@ -63,18 +62,18 @@ elasticsearch[async],0 envier,==0.5.2 exceptiongroup,0 faiss-cpu,==1.8.0 -falcon,~=3.0.0 +falcon,~=3.0 fastapi,~=0.64.0 flask,~=0.12.0 flask-caching,~=1.10.0 flask-openapi3,0 gevent,~=20.12.0 -git+https://github.com/gnufede/pytest-memray.git@24a3c0735db99eedf57fb36c573680f9bab7cd73,0 +google-generativeai,0 googleapis-common-protos,0 graphene,~=3.0.0 graphql-core,~=3.2.0 graphql-relay,0 -greenlet,~=1.0.0 +greenlet,~=1.0 grpcio,~=1.34.0 gunicorn,==20.0.4 gunicorn[gevent],0 @@ -85,7 +84,7 @@ hypothesis,<6.45.1 importlib-metadata,0 importlib_metadata,<5.0 itsdangerous,<2.0 -jinja2,~=2.11.0 +jinja2,~=2.10.0 kombu,>=4.2.0 langchain,==0.0.192 langchain-anthropic,==0.1.11 @@ -144,12 +143,16 @@ pytest-aiohttp,0 pytest-bdd,>=4.0 pytest-benchmark,>=3.1.0 pytest-cov,==2.9.0 +pytest-cpp,0 pytest-django[testing],==3.10.0 +pytest-memray,~=1.7.0 pytest-mock,==2.0.0 pytest-sanic,~=1.6.2 python-consul,>=1.1 python-json-logger,==2.0.7 python-memcached,0 +python-multipart,0 +ragas,==0.1.21 redis,~=2.0 redis-py-cluster,>=2.0 reno,0 diff --git a/releasenotes/notes/lower-minima-8c969f5a71402e01.yaml b/releasenotes/notes/lower-minima-8c969f5a71402e01.yaml new file mode 100644 index 00000000000..35d624a4420 --- /dev/null +++ b/releasenotes/notes/lower-minima-8c969f5a71402e01.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + This fix resolves an issue where the default versions of ``click`` and ``jinja2`` installed on 3.8 were outside of the + allowed minimum versions for autoinstrumentation. diff --git a/riotfile.py b/riotfile.py index 64829e61b96..b5414c64c93 100644 --- a/riotfile.py +++ b/riotfile.py @@ -10,18 +10,17 @@ latest = "" -SUPPORTED_PYTHON_VERSIONS = [ +SUPPORTED_PYTHON_VERSIONS: List[Tuple[int, int]] = [ (3, 7), (3, 8), (3, 9), (3, 10), (3, 11), (3, 12), -] # type: List[Tuple[int, int]] +] -def version_to_str(version): - # type: (Tuple[int, int]) -> str +def version_to_str(version: Tuple[int, int]) -> str: """Convert a Python version tuple to a string >>> version_to_str((3, 7)) @@ -42,8 +41,7 @@ def version_to_str(version): return ".".join(str(p) for p in version) -def str_to_version(version): - # type: (str) -> Tuple[int, int] +def str_to_version(version: str) -> Tuple[int, int]: """Convert a Python version string to a tuple >>> str_to_version("3.7") @@ -68,8 +66,7 @@ def str_to_version(version): MAX_PYTHON_VERSION = version_to_str(max(SUPPORTED_PYTHON_VERSIONS)) -def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): - # type: (str, str) -> List[str] +def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYTHON_VERSION) -> List[str]: """Helper to select python versions from the list of versions we support >>> select_pys() @@ -1116,7 +1113,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "pytest": "~=4.0", "pytest-mock": "==2.0.0", "pytest-cov": "~=3.0", - "Jinja2": "~=2.11.0", + "Jinja2": "~=2.10.0", "more_itertools": "<8.11.0", # https://github.com/pallets/itsdangerous/issues/290 # DEV: Breaking change made in 2.0 release @@ -1270,7 +1267,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "pynamodb": ["~=5.0", "~=5.3", "<6.0"], "moto": ">=1.0,<2.0", "cfn-lint": "~=0.53.1", - "Jinja2": "~=2.11.0", + "Jinja2": "~=2.10.0", "pytest-randomly": latest, }, ), @@ -2051,7 +2048,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): Venv( pys=select_pys(max_version="3.9"), pkgs={ - "jinja2": "~=2.11.0", + "jinja2": "~=2.10.0", # https://github.com/pallets/markupsafe/issues/282 # DEV: Breaking change made in 2.1.0 release "markupsafe": "<2.0", diff --git a/scripts/min_compatible_versions.py b/scripts/min_compatible_versions.py index 42695b88f03..ae35049f02b 100644 --- a/scripts/min_compatible_versions.py +++ b/scripts/min_compatible_versions.py @@ -14,7 +14,7 @@ OUT_FILENAME = "min_compatible_versions.csv" OUT_DIRECTORIES = (".", "lib-injection/sources") -IGNORED_PACKAGES = {"setuptools", "attrs", "pytest-randomly", "pillow", "botocore", "pytest-asyncio"} +IGNORED_PACKAGES = {"setuptools", "attrs", "pytest-randomly", "pillow", "botocore", "pytest-asyncio", "click"} def _format_version_specifiers(spec: Set[str]) -> Set[str]: From 110dcfae02e08b5a5fe2c7419549abcefde13bfa Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:10:57 -0500 Subject: [PATCH 195/372] chore(service-naming): improve inferred service naming algorithm (#11458) ## Motivation Updates inferred service naming algorithm. Previously, the service naming algorithm would skip over any flag arguments (args starting with `-`), as well as the following argument as it was assumed to be the argument value. The update changes the algorithm to run again if no service name was found the first time. The second time, the algorithm will not skip arguments that were preceded by a flag argument. Effect: -- Previous Behavior: `pytest --no-cov my/file.py` -> service name = `""` -- New Behavior: `pytest --no-cov my/file.py` -> service name = `"my.file"` Additionally adds check to ensure a python module included after `-m` module is importable before using it as the service name. Also updates the service naming algorithm to use try-catches to prevent any unforeseen errors. Adds many more test cases, fixes a few snapshots. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/_inferred_base_service.py | 123 +++++++++++------- tests/conftest.py | 14 ++ tests/contrib/gunicorn/test_gunicorn.py | 4 +- .../test_inferred_base_service.py | 105 ++++++++++++++- ...n.test_span_schematization[None-None].json | 8 +- ...orn.test_span_schematization[None-v0].json | 8 +- ...orn.test_span_schematization[None-v1].json | 12 +- ...a.test_kafka.test_analytics_with_rate.json | 2 +- ...est_kafka.test_analytics_without_rate.json | 2 +- ....contrib.kafka.test_kafka.test_commit.json | 4 +- ...st_commit_with_consume_single_message.json | 4 +- ...commit_with_consume_with_error[False].json | 4 +- ...t_with_consume_with_multiple_messages.json | 6 +- ...ka.test_kafka.test_commit_with_offset.json | 4 +- ...kafka.test_commit_with_only_async_arg.json | 4 +- ....kafka.test_kafka.test_message[False].json | 2 +- ...b.kafka.test_kafka.test_message[True].json | 2 +- ...afka.test_kafka.test_service_override.json | 4 +- 18 files changed, 227 insertions(+), 85 deletions(-) diff --git a/ddtrace/settings/_inferred_base_service.py b/ddtrace/settings/_inferred_base_service.py index a7d01c09dd8..16260629add 100644 --- a/ddtrace/settings/_inferred_base_service.py +++ b/ddtrace/settings/_inferred_base_service.py @@ -1,4 +1,5 @@ import fnmatch +import importlib.util import os import pathlib import re @@ -8,6 +9,11 @@ from typing import Optional from typing import Tuple +from ..internal.logger import get_logger + + +log = get_logger(__name__) + INIT_PY = "__init__.py" ALL_PY_FILES = "*.py" @@ -33,7 +39,7 @@ def __init__(self, environ: Dict[str, str]): # - Match /python, /python3.7, etc. self.pattern = r"(^|/)(?!.*\.py$)(" + re.escape("python") + r"(\d+\.\d+)?$)" - def detect(self, args: List[str]) -> Optional[ServiceMetadata]: + def detect(self, args: List[str], skip_args_preceded_by_flags=True) -> Optional[ServiceMetadata]: """ Detects and returns service metadata based on the provided list of arguments. @@ -59,22 +65,27 @@ def detect(self, args: List[str]) -> Optional[ServiceMetadata]: has_flag_prefix = arg.startswith("-") and not arg.startswith("--ddtrace") is_env_variable = "=" in arg - should_skip_arg = prev_arg_is_flag or has_flag_prefix or is_env_variable + should_skip_arg = (prev_arg_is_flag and skip_args_preceded_by_flags) or has_flag_prefix or is_env_variable if module_flag: - return ServiceMetadata(arg) + if _module_exists(arg): + return ServiceMetadata(arg) if not should_skip_arg: - abs_path = pathlib.Path(arg).resolve() - if not abs_path.exists(): - continue - stripped = abs_path - if not stripped.is_dir(): - stripped = stripped.parent - value, ok = self.deduce_package_name(stripped) - if ok: - return ServiceMetadata(value) - return ServiceMetadata(self.find_nearest_top_level(stripped)) + try: + abs_path = pathlib.Path(arg).resolve() + if not abs_path.exists(): + continue + stripped = abs_path + if not stripped.is_dir(): + stripped = stripped.parent + value, ok = self.deduce_package_name(stripped) + if ok: + return ServiceMetadata(value) + return ServiceMetadata(self.find_nearest_top_level(stripped)) + except Exception as ex: + # Catch any unexpected errors + log.debug("Unexpected error while processing argument: ", arg, "Exception: ", ex) if has_flag_prefix and arg == "-m": module_flag = True @@ -145,39 +156,51 @@ def detect_service(args: List[str]) -> Optional[str]: if cache_key in CACHE: return CACHE.get(cache_key) - # Check both the included command args as well as the executable being run - possible_commands = [*args, sys.executable] - executable_args = set() - - # List of detectors to try in order - detectors = {} - for detector_class in detector_classes: - detector_instance = detector_class(dict(os.environ)) - - for i, command in enumerate(possible_commands): - detector_name = detector_instance.name - - if detector_instance.matches(command): - detectors.update({detector_name: detector_instance}) - # append to a list of arg indexes to ignore since they are executables - executable_args.add(i) - continue - elif _is_executable(command): - # append to a list of arg indexes to ignore since they are executables - executable_args.add(i) - - args_to_search = [] - for i, arg in enumerate(args): - # skip any executable args - if i not in executable_args: - args_to_search.append(arg) - - # Iterate through the matched detectors - for detector in detectors.values(): - metadata = detector.detect(args_to_search) - if metadata and metadata.name: - CACHE[cache_key] = metadata.name - return metadata.name + try: + # Check both the included command args as well as the executable being run + possible_commands = [*args, sys.executable] + executable_args = set() + + # List of detectors to try in order + detectors = {} + for detector_class in detector_classes: + detector_instance = detector_class(dict(os.environ)) + + for i, command in enumerate(possible_commands): + detector_name = detector_instance.name + + if detector_instance.matches(command): + detectors.update({detector_name: detector_instance}) + # append to a list of arg indexes to ignore since they are executables + executable_args.add(i) + continue + elif _is_executable(command): + # append to a list of arg indexes to ignore since they are executables + executable_args.add(i) + + args_to_search = [] + for i, arg in enumerate(args): + # skip any executable args + if i not in executable_args: + args_to_search.append(arg) + + # Iterate through the matched detectors + for detector in detectors.values(): + metadata = detector.detect(args_to_search) + if metadata and metadata.name: + CACHE[cache_key] = metadata.name + return metadata.name + + # Iterate through the matched detectors again, this time not skipping args preceded by flag args + for detector in detectors.values(): + metadata = detector.detect(args_to_search, skip_args_preceded_by_flags=False) + if metadata and metadata.name: + CACHE[cache_key] = metadata.name + return metadata.name + except Exception as ex: + # Catch any unexpected errors to be extra safe + log.warning("Unexpected error during inferred base service detection: ", ex) + CACHE[cache_key] = None return None @@ -195,3 +218,11 @@ def _is_executable(file_path: str) -> bool: directory = os.path.dirname(directory) return False + + +def _module_exists(module_name: str) -> bool: + """Check if a module can be imported.""" + try: + return importlib.util.find_spec(module_name) is not None + except ModuleNotFoundError: + return False diff --git a/tests/conftest.py b/tests/conftest.py index c4c21caddf0..5ee2933f187 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -178,6 +178,20 @@ def create_ddtrace_subprocess_dir_and_return_test_pyfile(tmpdir): return pyfile +@pytest.fixture +def ddtrace_tmp_path(tmp_path): + # Create a test dir named `ddtrace_subprocess_dir` that will be used by the tracers + ddtrace_dir = tmp_path / DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME + ddtrace_dir.mkdir(exist_ok=True) # Create the directory if it doesn't exist + + # Check for __init__.py and create it if it doesn't exist + init_file = ddtrace_dir / "__init__.py" + if not init_file.exists(): + init_file.write_text("") # Create an empty __init__.py file + + return ddtrace_dir + + @pytest.fixture def run_python_code_in_subprocess(tmpdir): def _run(code, **kwargs): diff --git a/tests/contrib/gunicorn/test_gunicorn.py b/tests/contrib/gunicorn/test_gunicorn.py index 5906e5bfbf3..41025611531 100644 --- a/tests/contrib/gunicorn/test_gunicorn.py +++ b/tests/contrib/gunicorn/test_gunicorn.py @@ -183,7 +183,7 @@ def test_no_known_errors_occur(tmp_path): @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Gunicorn is only supported up to 3.10") -def test_span_schematization(tmp_path): +def test_span_schematization(ddtrace_tmp_path): for schema_version in [None, "v0", "v1"]: for service_name in [None, "mysvc"]: gunicorn_settings = _gunicorn_settings_factory( @@ -197,7 +197,7 @@ def test_span_schematization(tmp_path): ), ignores=["meta.result_class"], ): - with gunicorn_server(gunicorn_settings, tmp_path) as context: + with gunicorn_server(gunicorn_settings, ddtrace_tmp_path) as context: _, client = context response = client.get("/") assert response.status_code == 200 diff --git a/tests/internal/service_name/test_inferred_base_service.py b/tests/internal/service_name/test_inferred_base_service.py index 1e9d40636e6..c9f43d6c6cd 100644 --- a/tests/internal/service_name/test_inferred_base_service.py +++ b/tests/internal/service_name/test_inferred_base_service.py @@ -1,9 +1,11 @@ import pathlib +import shlex import tempfile from unittest.mock import patch import pytest +from ddtrace.settings._inferred_base_service import _module_exists from ddtrace.settings._inferred_base_service import detect_service @@ -21,7 +23,9 @@ def mock_file_system(): (base_path / "venv" / "bin" / "gunicorn").mkdir(parents=True) # add a test dir - (base_path / "tests" / "contrib" / "aiohttp").mkdir(parents=True) + (base_path / "tests" / "contrib" / "aiohttp" / "app").mkdir(parents=True) + + # other cases (base_path / "modules" / "m1" / "first" / "nice" / "package").mkdir(parents=True) (base_path / "modules" / "m2").mkdir(parents=True) @@ -35,15 +39,19 @@ def mock_file_system(): (base_path / "venv" / "bin" / "python3.11" / "gunicorn" / "__init__.py").mkdir(parents=True) (base_path / "venv" / "bin" / "gunicorn" / "__init__.py").touch() + # Create `__init__.py` files that indicate packages (base_path / "modules" / "m1" / "first" / "nice" / "package" / "__init__.py").touch() (base_path / "modules" / "m1" / "first" / "nice" / "__init__.py").touch() - (base_path / "modules" / "m1" / "first" / "nice" / "something.py").touch() + (base_path / "modules" / "m1" / "first" / "nice" / "app.py").touch() (base_path / "modules" / "m1" / "first" / "__init__.py").touch() (base_path / "modules" / "m1" / "__init__.py").touch() + (base_path / "modules" / "m2" / "__init__.py").touch() (base_path / "apps" / "app1" / "__main__.py").touch() (base_path / "apps" / "app2" / "cmd" / "run.py").touch() (base_path / "apps" / "app2" / "setup.py").touch() + (base_path / "tests" / "contrib" / "aiohttp" / "app" / "web.py").touch() + (base_path / "tests" / "contrib" / "aiohttp" / "app" / "__init__.py").touch() (base_path / "tests" / "contrib" / "aiohttp" / "test.py").touch() (base_path / "tests" / "contrib" / "aiohttp" / "__init__.py").touch() (base_path / "tests" / "contrib" / "__init__.py").touch() @@ -59,9 +67,13 @@ def mock_file_system(): @pytest.mark.parametrize( "cmd,expected", [ + ("python tests/contrib/aiohttp/app/web.py", "tests.contrib.aiohttp.app"), + ("python tests/contrib/aiohttp", "tests.contrib.aiohttp"), + ("python tests/contrib", "tests.contrib"), + ("python tests", "tests"), ("python modules/m1/first/nice/package", "m1.first.nice.package"), ("python modules/m1/first/nice", "m1.first.nice"), - ("python modules/m1/first/nice/something.py", "m1.first.nice"), + ("python modules/m1/first/nice/app.py", "m1.first.nice"), ("python modules/m1/first", "m1.first"), ("python modules/m2", "m2"), ("python apps/app1", "app1"), @@ -75,17 +87,98 @@ def mock_file_system(): ("venv/bin/python3.11/ddtrace-run python apps/app2/setup.py", "app2"), ("ddtrace-run python apps/app2/setup.py", "app2"), ("python3.12 apps/app2/cmd/run.py", "app2"), - ("python -m m1.first.nice.package", "m1.first.nice.package"), + ("python -m tests.contrib.aiohttp.app.web", "tests.contrib.aiohttp.app.web"), + ("python -m tests.contrib.aiohttp.app", "tests.contrib.aiohttp.app"), + ("python -m tests.contrib.aiohttp", "tests.contrib.aiohttp"), + ("python -m tests.contrib", "tests.contrib"), + ("python -m tests", "tests"), ("python -m http.server 8000", "http.server"), + ("python --some-flag apps/app1", "app1"), # pytest ("pytest tests/contrib/aiohttp", "tests.contrib.aiohttp"), ("pytest --ddtrace tests/contrib/aiohttp", "tests.contrib.aiohttp"), + ("pytest --no-cov tests/contrib/aiohttp", "tests.contrib.aiohttp"), ], ) -def test_python_detector(cmd, expected, mock_file_system): +def test_python_detector_service_name_should_exist_file_exists(cmd, expected, mock_file_system): # Mock the current working directory to the test_modules path with patch("os.getcwd", return_value=str(mock_file_system)): - cmd_parts = cmd.split(" ") + cmd_parts = shlex.split(cmd) detected_name = detect_service(cmd_parts) assert detected_name == expected, f"Test failed for command: [{cmd}]" + + +@pytest.mark.parametrize( + "cmd,expected", + [ + # Commands that should not produce a service name + ("", None), # Empty command + ("python non_existing_file.py", None), # Non-existing Python script + ("python invalid_script.py", None), # Invalid script that isn't found + ("gunicorn app:app", None), # Non-Python command + ("ls -la", None), # Non-Python random command + ("cat README.md", None), # Another random command + ("python -m non_existing_module", None), # Non-existing Python module + ("python -c 'print([])'", None), # Python inline code not producing a service + ("python -m -c 'print([]])'", None), # Inline code with module flag + ("echo 'Hello, World!'", None), # Not a Python service + ("python3.11 /path/to/some/non_python_file.txt", None), # Non-Python file + ("/usr/bin/ls", None), # Another system command + ("some_executable --ddtrace hello", None), + ("python version", None), + ("python -m -v --hello=maam", None), + # error produced from a test, ensure an arg that is very long doesn't break stuff + ( + "ddtrace-run pytest -k 'not test_reloader and not test_reload_listeners and not " + + "test_no_exceptions_when_cancel_pending_request and not test_add_signal and not " + + "test_ode_removes and not test_skip_touchup and not test_dispatch_signal_triggers" + + " and not test_keep_alive_connection_context and not test_redirect_with_params and" + + " not test_keep_alive_client_timeout and not test_logger_vhosts and not test_ssl_in_multiprocess_mode'", + None, + ), + ], +) +def test_no_service_name(cmd, expected, mock_file_system): + with patch("os.getcwd", return_value=str(mock_file_system)): + cmd_parts = shlex.split(cmd) + detected_name = detect_service(cmd_parts) + + assert detected_name == expected, f"Test failed for command: [{cmd}]" + + +@pytest.mark.parametrize( + "cmd,expected", + [ + # Command that is too long + ("python " + " ".join(["arg"] * 1000), None), # Excessively long command + # Path with special characters + (r"python /path/with/special/characters/!@#$%^&*()_/some_script.py", None), # Special characters + # Path too deep + (f"python {'/'.join(['deep' * 50])}/script.py", None), # Excessively deep path + ], +) +def test_chaos(cmd, expected, mock_file_system): + with patch("os.getcwd", return_value=str(mock_file_system)): + cmd_parts = shlex.split(cmd) + detected_name = detect_service(cmd_parts) + + assert detected_name == expected, f"Chaos test failed for command: [{cmd}]" + + +@pytest.mark.parametrize( + "module_name,should_exist", + [ + ("tests.contrib.aiohttp.app.web", True), + ("tests.contrib.aiohttp.app", True), + ("tests.contrib.aiohttp", True), + ("tests.contrib", True), + ("tests", True), + ("tests.releasenotes", False), + ("non_existing_module", False), + ], +) +def test_module_exists(module_name, should_exist): + exists = _module_exists(module_name) + + assert exists == should_exist, f"Module {module_name} existence check failed." diff --git a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-None].json b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-None].json index 7e1a6653dc4..a03482d3db5 100644 --- a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-None].json +++ b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-None].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "listiterator" diff --git a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v0].json b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v0].json index c4817253f49..d00ca416edd 100644 --- a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v0].json +++ b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v0].json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -40,7 +40,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -57,7 +57,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -75,7 +75,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "listiterator" diff --git a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v1].json b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v1].json index 04d4735906d..2ab909b8129 100644 --- a/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v1].json +++ b/tests/snapshots/tests.contrib.gunicorn.test_gunicorn.test_span_schematization[None-v1].json @@ -1,7 +1,7 @@ [[ { "name": "http.server.request", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "GET /", "trace_id": 0, "span_id": 1, @@ -9,6 +9,7 @@ "type": "web", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "wsgi", @@ -31,7 +32,7 @@ }, { "name": "wsgi.application", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.application", "trace_id": 0, "span_id": 2, @@ -39,6 +40,7 @@ "type": "", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi" }, @@ -47,7 +49,7 @@ }, { "name": "wsgi.start_response", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.start_response", "trace_id": 0, "span_id": 4, @@ -55,6 +57,7 @@ "type": "web", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "span.kind": "server" @@ -64,7 +67,7 @@ }, { "name": "wsgi.response", - "service": "unnamed-python-service", + "service": "wsgi", "resource": "wsgi.response", "trace_id": 0, "span_id": 3, @@ -72,6 +75,7 @@ "type": "", "error": 0, "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", "_dd.p.tid": "654a694400000000", "component": "wsgi", "result_class": "list_iterator" diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json index 1aaec31a025..04eb7c0c7c4 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_with_rate.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json index 689eba782a0..707f3fe59d2 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_analytics_without_rate.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json index 4302cc6a66c..fea7d090bc2 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json index 64398306ae1..f186e82b48d 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_single_message.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65dcd1fd00000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65dcd1f900000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json index f918fdb6061..936b32bc878 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_error[False].json @@ -9,7 +9,7 @@ "type": "worker", "error": 1, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65df90df00000000", "component": "kafka", @@ -44,7 +44,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65df90dd00000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json index b712bb12f4f..3f569c7fa4a 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_consume_with_multiple_messages.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12500000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12000000000", "component": "kafka", @@ -85,7 +85,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "65e0d12000000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json index b49954a04bf..8785ba6328b 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_offset.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json index 896d5024ab9..d07bcb6d2f6 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_commit_with_only_async_arg.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json index 10dc4084f33..0bd46473c60 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[False].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json index f3088a8cfd6..798de6d29b9 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_message[True].json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", diff --git a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json index cf977598cbb..906504a3c1e 100644 --- a/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json +++ b/tests/snapshots/tests.contrib.kafka.test_kafka.test_service_override.json @@ -9,7 +9,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", @@ -48,7 +48,7 @@ "type": "worker", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.kafka", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "kafka", From d5ac5f9d848a5505cecbd3732c4ab13c2cbcc160 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 20 Nov 2024 22:50:57 -0500 Subject: [PATCH 196/372] chore(profiling): gunicorn test improvements (#11471) - use `--worker-tmp-dir /dev/shm` as in https://pythonspeed.com/articles/gunicorn-in-docker/, https://docs.gunicorn.org/en/stable/faq.html#how-do-i-avoid-gunicorn-excessively-blocking-in-os-fchmod - clean up server when the request fails ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling_v2/test_gunicorn.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index cf798174d9b..4d7adbf6c95 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -30,7 +30,16 @@ def debug_print(*args): def _run_gunicorn(*args): cmd = ( - ["ddtrace-run", "gunicorn", "--bind", "127.0.0.1:7644", "--chdir", os.path.dirname(__file__)] + [ + "ddtrace-run", + "gunicorn", + "--bind", + "127.0.0.1:7644", + "--worker-tmp-dir", + "/dev/shm", + "--chdir", + os.path.dirname(__file__), + ] + list(args) + ["tests.profiling.gunicorn-app:app"] ) @@ -75,6 +84,9 @@ def _test_gunicorn(gunicorn, tmp_path, monkeypatch, *args): response = f.read().decode() debug_print(response) except Exception as e: + proc.terminate() + output = proc.stdout.read().decode() + print(output) pytest.fail("Failed to make request to gunicorn server %s" % e) finally: # Need to terminate the process to get the output and release the port From 74e3d515a6a088883d8a2d54f9f880eb4e147d99 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 21 Nov 2024 08:41:25 +0100 Subject: [PATCH 197/372] feat: iast execute source metric (#11445) - Attach execute source in the span metrics - Refactor IAST span metrics to use iast context instead of a global variable ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Federico Mon --- ddtrace/appsec/_constants.py | 1 + ddtrace/appsec/_iast/_iast_request_context.py | 2 ++ ddtrace/appsec/_iast/_metrics.py | 25 ++++++++++++++++--- .../appsec/_iast/_taint_tracking/__init__.py | 3 +++ tests/appsec/iast/test_telemetry.py | 1 + 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 4b7aafa3e10..e599bd92a2b 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -153,6 +153,7 @@ class IAST_SPAN_TAGS(metaclass=Constant_Class): TELEMETRY_REQUEST_TAINTED: Literal["_dd.iast.telemetry.request.tainted"] = "_dd.iast.telemetry.request.tainted" TELEMETRY_EXECUTED_SINK: Literal["_dd.iast.telemetry.executed.sink"] = "_dd.iast.telemetry.executed.sink" + TELEMETRY_EXECUTED_SOURCE: Literal["_dd.iast.telemetry.executed.source"] = "_dd.iast.telemetry.executed.source" class WAF_DATA_NAMES(metaclass=Constant_Class): diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index becc2bcd49a..b3a01a97cca 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -1,4 +1,5 @@ import sys +from typing import Dict from typing import Optional from ddtrace._trace.span import Span @@ -47,6 +48,7 @@ def __init__(self, span: Optional[Span] = None): self.request_enabled: bool = False self.iast_reporter: Optional[IastSpanReporter] = None + self.iast_span_metrics: Dict[str, int] = {} def _get_iast_context() -> Optional[IASTEnvironment]: diff --git a/ddtrace/appsec/_iast/_metrics.py b/ddtrace/appsec/_iast/_metrics.py index 6ddc3352fe2..e9e0f604e69 100644 --- a/ddtrace/appsec/_iast/_metrics.py +++ b/ddtrace/appsec/_iast/_metrics.py @@ -132,22 +132,39 @@ def _set_span_tag_iast_executed_sink(span): if data is not None: for key, value in data.items(): - if key.startswith(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK): + if key.startswith(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK) or key.startswith( + IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SOURCE + ): span.set_tag(key, value) reset_iast_span_metrics() +def _metric_key_as_snake_case(key): + from ._taint_tracking import OriginType + + if isinstance(key, OriginType): + from ._taint_tracking import origin_to_str + + key = origin_to_str(key) + key = key.replace(".", "_") + return key.lower() + + def increment_iast_span_metric(prefix: str, metric_key: str, counter: int = 1) -> None: data = get_iast_span_metrics() - full_key = prefix + "." + metric_key.lower() + full_key = prefix + "." + _metric_key_as_snake_case(metric_key) result = data.get(full_key, 0) data[full_key] = result + counter def get_iast_span_metrics() -> Dict: - return _IAST_SPAN_METRICS + from ddtrace.appsec._iast._iast_request_context import _get_iast_context + + env = _get_iast_context() + return env.iast_span_metrics if env else dict() def reset_iast_span_metrics() -> None: - _IAST_SPAN_METRICS.clear() + metrics = get_iast_span_metrics() + metrics.clear() diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 00d676758fa..a6bad81f64c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -9,9 +9,11 @@ from ddtrace.internal.logger import get_logger from ..._constants import IAST +from ..._constants import IAST_SPAN_TAGS from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_iast_error_metric from .._metrics import _set_metric_iast_executed_source +from .._metrics import increment_iast_span_metric from .._utils import _is_iast_debug_enabled from .._utils import _is_iast_propagation_debug_enabled from .._utils import _is_python_version_supported @@ -181,6 +183,7 @@ def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_or res = _taint_pyobject_base(pyobject, source_name, source_value, source_origin) _set_metric_iast_executed_source(source_origin) + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SOURCE, source_origin) return res except ValueError as e: log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e) diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 5ed79e66b48..95b9b8aeb45 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -170,6 +170,7 @@ def test_metric_request_tainted(no_request_sampling, telemetry_writer): assert filtered_metrics == ["executed.source", "request.tainted"] assert len(filtered_metrics) == 2, "Expected 2 generate_metrics" assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) > 0 + assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SOURCE + ".http_request_parameter") > 0 @pytest.mark.skip_iast_check_logs From 0ba6945ad22a1a91b22a1c9678e8e162bdba102c Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 21 Nov 2024 14:31:49 +0100 Subject: [PATCH 198/372] chore(iast): add tool to debug iast patched code (#11468) Co-authored-by: Juanjo Alvarez --- scripts/iast/print_patched_code.py | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 scripts/iast/print_patched_code.py diff --git a/scripts/iast/print_patched_code.py b/scripts/iast/print_patched_code.py new file mode 100644 index 00000000000..7195fff53aa --- /dev/null +++ b/scripts/iast/print_patched_code.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +""" +Debug script to print the patched code of a Python file, optionally colorized (if you have pygments) +and formatted (if you have black). +Usage: PYTHONPATH=$PYTHONPATH:~/.../dd-trace-py/ python -m scripts.iast.print_patched_code /path/to/your/python_file.py +""" + +import os +import sys +from typing import Text + + +try: + from pygments import highlight + from pygments.formatters import TerminalFormatter + from pygments.lexers import PythonLexer + + colorize = True +except ImportError: + colorize = False + +try: + import black + + _format = True +except ImportError: + _format = False + +from ddtrace.appsec._iast._ast.ast_patching import astpatch_module + + +def _get_patched_code(module_path: Text) -> str: + import astunparse + + module_dir = os.path.dirname(module_path) + sys.path.append(module_dir) + module_name = os.path.splitext(os.path.basename(module_path))[0] + _, new_ast = astpatch_module(__import__(module_name)) + return astunparse.unparse(new_ast) + + +if __name__ == "__main__": + MODULE_PATH = sys.argv[1] + code = _get_patched_code(MODULE_PATH) + + if _format: + code = black.format_str(code, mode=black.FileMode()) + + if colorize: + code = highlight(code, PythonLexer(), TerminalFormatter()) + + print(code) From 83ded1333930e17a87f88c2d81efa02401d526e2 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 21 Nov 2024 09:50:22 -0500 Subject: [PATCH 199/372] chore: upgrade ddsketch/libdatadog to v14.1.0 (#11470) --- src/core/Cargo.lock | 4 ++-- src/core/Cargo.toml | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/Cargo.lock b/src/core/Cargo.lock index e752d11e57b..f1d3a6c47ff 100644 --- a/src/core/Cargo.lock +++ b/src/core/Cargo.lock @@ -34,8 +34,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "datadog-ddsketch" -version = "11.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=bfeef6d3f079c4f3f191176068353f82d5702b1d#bfeef6d3f079c4f3f191176068353f82d5702b1d" +version = "14.1.0" +source = "git+https://github.com/DataDog/libdatadog?rev=v14.1.0#106fe1aee81c912e3201b7d138d57dabdfed4295" dependencies = [ "prost", ] diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index f972b216738..2ff16e20e1e 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -10,9 +10,7 @@ opt-level = 3 [dependencies] pyo3 = { version = "0.21.2", features = ["extension-module"] } - -# TODO: Use latest version tag instead of a specific commit -datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "bfeef6d3f079c4f3f191176068353f82d5702b1d" } +datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v14.1.0" } [build-dependencies] pyo3-build-config = "0.21.2" From e2f6e60250ab4c14edaa6af5258631d8ff324cea Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 21 Nov 2024 10:24:02 -0500 Subject: [PATCH 200/372] chore(profiling): cmake clean ups (#11457) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../profiling/cmake/AnalysisFunc.cmake | 11 ++++++++ .../profiling/crashtracker/CMakeLists.txt | 28 +++++-------------- .../profiling/dd_wrapper/CMakeLists.txt | 4 +-- .../datadog/profiling/ddup/CMakeLists.txt | 28 +++++-------------- .../datadog/profiling/stack_v2/CMakeLists.txt | 7 ----- 5 files changed, 26 insertions(+), 52 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake index 7ea9247d45b..b95a1205a62 100644 --- a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake @@ -1,6 +1,12 @@ include(CheckIPOSupported) function(add_ddup_config target) + # Profiling native extensions are built with C++17, even though underlying + # repo adheres to the manylinux 2014 standard. This isn't currently a + # problem, but if it becomes one, we may have to structure the library + # differently. + target_compile_features(${target} PUBLIC cxx_std_17) + # Common compile options target_compile_options(${target} PRIVATE "$<$:-Os>" -ffunction-sections -Wall @@ -82,4 +88,9 @@ function(add_ddup_config target) if(DO_FANALYZER AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${target} PRIVATE -fanalyzer) endif() + + # The main targets, ddup, crashtracker, stack_v2, and dd_wrapper are built + # as dynamic libraries, so PIC is required. And setting this is also fine + # for tests as they're loading those dynamic libraries. + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) endfunction() diff --git a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt index d17ad669667..a38b70fc224 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt @@ -44,24 +44,13 @@ add_custom_command( # Specify the target C-extension that we want to build add_library(${EXTENSION_NAME} SHARED ${CRASHTRACKER_CPP_SRC}) -# We can't add common Profiling configuration because cython generates messy code, so we just setup some basic flags and -# features -target_compile_options(${EXTENSION_NAME} PRIVATE "$<$:-Os>" -ffunction-sections) - -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # macOS-specific options - target_compile_options(${EXTENSION_NAME} PRIVATE "$<$:-Og;-g>") - target_link_options(${EXTENSION_NAME} PRIVATE "$<$:-Wl,-dead_strip>") -else() - # Linux/ELF-based options - target_compile_options(${EXTENSION_NAME} PRIVATE "$<$:-Og;-ggdb3>" -fno-semantic-interposition) - target_link_options(${EXTENSION_NAME} PRIVATE "$<$:-s>" -Wl,--as-needed -Wl,-Bsymbolic-functions - -Wl,--gc-sections) -endif() - -set_property(TARGET ${EXTENSION_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) - -target_compile_features(${EXTENSION_NAME} PUBLIC cxx_std_17) +add_ddup_config(${EXTENSION_NAME}) +# Cython generates code that produces errors for the following, so relax compile +# options +target_compile_options( + ${EXTENSION_NAME} + PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address +) # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. @@ -81,9 +70,6 @@ else() target_link_libraries(${EXTENSION_NAME} PRIVATE dd_wrapper) endif() -# Extensions are built as dynamic libraries, so PIC is required. -set_target_properties(${EXTENSION_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # Set the output directory for the built library if(LIB_INSTALL_DIR) install( diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt index 5dce448919f..809569d8493 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt @@ -47,8 +47,6 @@ add_library( # Add common configuration flags add_ddup_config(dd_wrapper) -target_compile_features(dd_wrapper PUBLIC cxx_std_17) - target_include_directories(dd_wrapper PRIVATE include ${Datadog_INCLUDE_DIRS}) target_link_libraries(dd_wrapper PRIVATE ${Datadog_LIBRARIES} Threads::Threads) @@ -75,7 +73,7 @@ string(TOLOWER ${PLATFORM_SUFFIX} PLATFORM_SUFFIX) # but as long as it encodes the major moving parts, it's fine set(DD_WRAPPER_TARGET_NAME "dd_wrapper-${PLATFORM_SUFFIX}") -set_target_properties(dd_wrapper PROPERTIES POSITION_INDEPENDENT_CODE ON OUTPUT_NAME "${DD_WRAPPER_TARGET_NAME}") +set_target_properties(dd_wrapper PROPERTIES OUTPUT_NAME "${DD_WRAPPER_TARGET_NAME}") # The name of the crashtracker executable has to be propagated to a a few different targets, and it needs to be inferred # at runtime, so we set it here. Any component which used dd_wrapper will have access to a uniform name. diff --git a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt index 920db8bf571..8aeb0f1c23a 100644 --- a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt @@ -49,24 +49,13 @@ add_custom_command( # Specify the target C-extension that we want to build add_library(${EXTENSION_NAME} SHARED ${DDUP_CPP_SRC}) -# We can't add common Profiling configuration because cython generates messy code, so we just setup some basic flags and -# features -target_link_options(${EXTENSION_NAME} PRIVATE "$<$:-Os>" -ffunction-sections) - -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # macOS-specific options - target_compile_options(${EXTENSION_NAME} PRIVATE "$<$:-Og;-g>") - target_link_options(${EXTENSION_NAME} PRIVATE "$<$:-Wl,-dead_strip>") -else() - # Linux/ELF-based options - target_compile_options(${EXTENSION_NAME} PRIVATE "$<$:-Og;-ggdb3>" -fno-semantic-interposition) - target_link_options(${EXTENSION_NAME} PRIVATE "$<$:-s>" -Wl,--as-needed -Wl,-Bsymbolic-functions - -Wl,--gc-sections) -endif() - -set_property(TARGET ${EXTENSION_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) - -target_compile_features(${EXTENSION_NAME} PUBLIC cxx_std_17) +add_ddup_config(${EXTENSION_NAME}) +# Cython generates code that produces errors for the following, so relax compile +# options +target_compile_options( + ${EXTENSION_NAME} + PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address +) # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. @@ -85,9 +74,6 @@ else() target_link_libraries(${EXTENSION_NAME} PRIVATE dd_wrapper) endif() -# Extensions are built as dynamic libraries, so PIC is required. -set_target_properties(${EXTENSION_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # Set the output directory for the built library if(LIB_INSTALL_DIR) install( diff --git a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt index 19f3955dab0..9fc78ee8046 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt @@ -67,10 +67,6 @@ add_cppcheck_target( SRC ${CMAKE_CURRENT_SOURCE_DIR}/src) -# This project is build with C++17, even though the underlying repo adheres to the manylinux 2014 standard. This isn't -# currently a problem, but if it becomes one, we may have to structure the library differently. -target_compile_features(${EXTENSION_NAME} PUBLIC cxx_std_17) - # Never build with native unwinding, since this is not currently used target_compile_definitions(${EXTENSION_NAME} PRIVATE UNWIND_NATIVE_DISABLE) @@ -106,9 +102,6 @@ if(Python3_LIBRARIES) target_link_libraries(${EXTENSION_NAME} PRIVATE ${Python3_LIBRARIES}) endif() -# Extensions are built as dynamic libraries, so PIC is required. -set_target_properties(${EXTENSION_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # Set the output directory for the built library if(LIB_INSTALL_DIR) install( From 832301c414f483356fb16409b9a548864fe7e261 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:05:30 +0100 Subject: [PATCH 201/372] fix(asm): standalone missing tag (#11452) This fix resolves a possibly missing ASM standalone tag on root spans. It was only detected previously on uwsgi on nightly system tests. - ensure that all root spans are tagged. APPSEC-55222 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/processor/__init__.py | 4 +++- .../notes/fix_asm_standalone_span_tag-60b38c1d9d2efbaa.yaml | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix_asm_standalone_span_tag-60b38c1d9d2efbaa.yaml diff --git a/ddtrace/_trace/processor/__init__.py b/ddtrace/_trace/processor/__init__.py index f8e2dbe749d..3657ee79c39 100644 --- a/ddtrace/_trace/processor/__init__.py +++ b/ddtrace/_trace/processor/__init__.py @@ -136,7 +136,9 @@ def process_trace(self, trace: List[Span]) -> Optional[List[Span]]: root_ctx = chunk_root._context if self.apm_opt_out: - chunk_root.set_metric(MK_APM_ENABLED, 0) + for span in trace: + if span._local_root_value is None: + span.set_metric(MK_APM_ENABLED, 0) # only trace sample if we haven't already sampled if root_ctx and root_ctx.sampling_priority is None: diff --git a/releasenotes/notes/fix_asm_standalone_span_tag-60b38c1d9d2efbaa.yaml b/releasenotes/notes/fix_asm_standalone_span_tag-60b38c1d9d2efbaa.yaml new file mode 100644 index 00000000000..f92914f931e --- /dev/null +++ b/releasenotes/notes/fix_asm_standalone_span_tag-60b38c1d9d2efbaa.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + ASM: This fix resolves an issue where some root span where not appropriately tagged for ASM standalone. From d792c3dc3c7452ed64524ea38d0b9c9116330a73 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 21 Nov 2024 11:56:29 -0500 Subject: [PATCH 202/372] chore(profiling): make gunicorn app to run shorter (#11485) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling/gunicorn-app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/profiling/gunicorn-app.py b/tests/profiling/gunicorn-app.py index b76e1babdff..22d774eb341 100644 --- a/tests/profiling/gunicorn-app.py +++ b/tests/profiling/gunicorn-app.py @@ -9,7 +9,7 @@ def fib(n): def app(environ, start_response): - response_body = "fib(35) is %d at pid %d tid %d" % (fib(35), os.getpid(), threading.get_ident()) + response_body = "fib(30) is %d at pid %d tid %d" % (fib(30), os.getpid(), threading.get_ident()) response_body = response_body.encode("utf-8") From 836d972e8bf5b897435b79e73d918e809e970047 Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Thu, 21 Nov 2024 14:15:42 -0500 Subject: [PATCH 203/372] chore(profiling): use raw allocator free for traceback buffers (#11481) Co-authored-by: sanchda <838104+sanchda@users.noreply.github.com> --- ddtrace/profiling/collector/_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/profiling/collector/_utils.h b/ddtrace/profiling/collector/_utils.h index a64065e584c..18b54a6491b 100644 --- a/ddtrace/profiling/collector/_utils.h +++ b/ddtrace/profiling/collector/_utils.h @@ -14,7 +14,7 @@ random_range(uint64_t max) #define DO_NOTHING(...) #define p_new(type, count) PyMem_RawMalloc(sizeof(type) * (count)) -#define p_delete(mem_p) PyMem_Free(mem_p); +#define p_delete(mem_p) PyMem_RawFree(mem_p); // Allocate at least 16 and 50% more than requested to avoid allocating items one by one. #define p_alloc_nr(x) (((x) + 16) * 3 / 2) #define p_realloc(p, count) \ From 351d9c45e72a5096da0b5b60934040fb5ff5a8be Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:06:43 -0500 Subject: [PATCH 204/372] feat(vertexai): [MLOB-1803] add apm tracing for vertex ai (#11236) This PR adds support for tracing Google's Vertex AI SDK for Python. This includes capturing prompt and completion data for the following methods: - `generate_content` of the vertexai.generative_models.GenerativeModel class - `generate_content_async` of the vertexai.generative_models.GenerativeModel class - `send_message` of the vertexai.generative_models.ChatSession class - `send_message_async` of the vertexai.generative_models.ChatSession class This instrumentation also supports tracing for streamed responses. ### Other Changes This PR moves 3 helper methods (`extract_model_name`, `tag_request_content_part`, and `tag_response_part`) to the new `ddtrace/llmobs/_integrations/utils.py` file since these helpers are identical across the vertexai and gemini integrations. ### Testing Similar to the Gemini library as mentioned [here](https://github.com/DataDog/dd-trace-py/pull/10503), testing for Vertex AI cannot be achieved with the vcrpy testing framework. In order to get around this, I mocked out the PredictionServiceClient used to forward requests made to a GenerativeModel instance. ### Note This PR only supports tracing for Vertex AI applications using Google models (i.e. tracing for the vertexai.language_model module is not supported). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim Co-authored-by: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Co-authored-by: kyle --- .github/CODEOWNERS | 3 + .riot/requirements/23eab38.txt | 53 ++ .riot/requirements/59e23ef.txt | 55 ++ .riot/requirements/692fe7a.txt | 55 ++ .riot/requirements/f12fa99.txt | 53 ++ ddtrace/_monkey.py | 1 + .../internal/google_generativeai/_utils.py | 75 +-- .../internal/google_generativeai/patch.py | 6 +- ddtrace/contrib/internal/vertexai/_utils.py | 258 +++++++++ ddtrace/contrib/internal/vertexai/patch.py | 134 +++++ ddtrace/contrib/vertexai/__init__.py | 96 ++++ ddtrace/llmobs/_integrations/__init__.py | 2 + ddtrace/llmobs/_integrations/utils.py | 74 +++ ddtrace/llmobs/_integrations/vertexai.py | 18 + riotfile.py | 9 + tests/.suitespec.json | 14 + tests/contrib/vertexai/__init__.py | 0 tests/contrib/vertexai/conftest.py | 66 +++ tests/contrib/vertexai/test_vertexai.py | 535 ++++++++++++++++++ tests/contrib/vertexai/test_vertexai_patch.py | 30 + tests/contrib/vertexai/utils.py | 173 ++++++ tests/llmobs/jobspec.yml | 5 + ...est_vertexai.test_vertexai_completion.json | 38 ++ ...rtexai.test_vertexai_completion_error.json | 35 ++ ...vertexai_completion_multiple_messages.json | 42 ++ ...texai.test_vertexai_completion_stream.json | 39 ++ ...test_vertexai_completion_stream_error.json | 36 ++ ....test_vertexai_completion_stream_tool.json | 41 ++ ...est_vertexai_completion_system_prompt.json | 39 ++ ...ertexai.test_vertexai_completion_tool.json | 40 ++ 30 files changed, 1954 insertions(+), 71 deletions(-) create mode 100644 .riot/requirements/23eab38.txt create mode 100644 .riot/requirements/59e23ef.txt create mode 100644 .riot/requirements/692fe7a.txt create mode 100644 .riot/requirements/f12fa99.txt create mode 100644 ddtrace/contrib/internal/vertexai/_utils.py create mode 100644 ddtrace/contrib/internal/vertexai/patch.py create mode 100644 ddtrace/contrib/vertexai/__init__.py create mode 100644 ddtrace/llmobs/_integrations/utils.py create mode 100644 ddtrace/llmobs/_integrations/vertexai.py create mode 100644 tests/contrib/vertexai/__init__.py create mode 100644 tests/contrib/vertexai/conftest.py create mode 100644 tests/contrib/vertexai/test_vertexai.py create mode 100644 tests/contrib/vertexai/test_vertexai_patch.py create mode 100644 tests/contrib/vertexai/utils.py create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt.json create mode 100644 tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 44ad3487cfd..a32aa169c7a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -134,6 +134,8 @@ ddtrace/contrib/internal/anthropic @DataDog/ml-observabilit ddtrace/contrib/anthropic @DataDog/ml-observability ddtrace/contrib/internal/google_generativeai @DataDog/ml-observability ddtrace/contrib/google_generativeai @DataDog/ml-observability +ddtrace/contrib/internal/vertexai @DataDog/ml-observability +ddtrace/contrib/vertexai @DataDog/ml-observability tests/llmobs @DataDog/ml-observability tests/contrib/openai @DataDog/ml-observability tests/contrib/langchain @DataDog/ml-observability @@ -142,6 +144,7 @@ tests/contrib/botocore/test_bedrock_llmobs.py @DataDog/ml-observabilit tests/contrib/botocore/bedrock_cassettes @DataDog/ml-observability tests/contrib/anthropic @DataDog/ml-observability tests/contrib/google_generativeai @DataDog/ml-observability +tests/contrib/vertexai @DataDog/ml-observability .gitlab/tests/llmobs.yml @DataDog/ml-observability # Remote Config diff --git a/.riot/requirements/23eab38.txt b/.riot/requirements/23eab38.txt new file mode 100644 index 00000000000..feed5849537 --- /dev/null +++ b/.riot/requirements/23eab38.txt @@ -0,0 +1,53 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/23eab38.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +docstring-parser==0.16 +google-api-core[grpc]==2.23.0 +google-auth==2.36.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/59e23ef.txt b/.riot/requirements/59e23ef.txt new file mode 100644 index 00000000000..a0284fd7200 --- /dev/null +++ b/.riot/requirements/59e23ef.txt @@ -0,0 +1,55 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/59e23ef.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +docstring-parser==0.16 +exceptiongroup==1.2.2 +google-api-core[grpc]==2.23.0 +google-auth==2.36.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/692fe7a.txt b/.riot/requirements/692fe7a.txt new file mode 100644 index 00000000000..2e2a00ab4e3 --- /dev/null +++ b/.riot/requirements/692fe7a.txt @@ -0,0 +1,55 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/692fe7a.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +docstring-parser==0.16 +exceptiongroup==1.2.2 +google-api-core[grpc]==2.23.0 +google-auth==2.36.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.0.2 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/f12fa99.txt b/.riot/requirements/f12fa99.txt new file mode 100644 index 00000000000..6ba3725dc6a --- /dev/null +++ b/.riot/requirements/f12fa99.txt @@ -0,0 +1,53 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f12fa99.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.7 +docstring-parser==0.16 +google-api-core[grpc]==2.23.0 +google-auth==2.36.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 73bd0cc2d5f..15778c2c95a 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -69,6 +69,7 @@ "aiobotocore": False, "httplib": False, "urllib3": False, + "vertexai": True, "vertica": True, "molten": True, "jinja2": True, diff --git a/ddtrace/contrib/internal/google_generativeai/_utils.py b/ddtrace/contrib/internal/google_generativeai/_utils.py index 5982f990b18..20a923e07cb 100644 --- a/ddtrace/contrib/internal/google_generativeai/_utils.py +++ b/ddtrace/contrib/internal/google_generativeai/_utils.py @@ -4,7 +4,9 @@ import wrapt from ddtrace.internal.utils import get_argument_value -from ddtrace.llmobs._utils import _get_attr +from ddtrace.llmobs._integrations.utils import get_generation_config_google +from ddtrace.llmobs._integrations.utils import tag_request_content_part_google +from ddtrace.llmobs._integrations.utils import tag_response_part_google class BaseTracedGenerateContentResponse(wrapt.ObjectProxy): @@ -61,19 +63,6 @@ async def __aiter__(self): self._dd_span.finish() -def _extract_model_name(instance): - """Extract the model name from the instance. - The Google Gemini Python SDK stores model names in the format `"models/{model_name}"` - so we do our best to return the model name instead of the full string. - """ - model_name = getattr(instance, "model_name", "") - if not model_name or not isinstance(model_name, str): - return "" - if "/" in model_name: - return model_name.split("/")[-1] - return model_name - - def _extract_api_key(instance): """Extract the API key from the model instance.""" client = getattr(instance, "_client", None) @@ -87,36 +76,6 @@ def _extract_api_key(instance): return getattr(client_options, "api_key", None) -def _tag_request_content_part(span, integration, part, part_idx, content_idx): - """Tag the generation span with request content parts.""" - text = _get_attr(part, "text", "") - function_call = _get_attr(part, "function_call", None) - function_response = _get_attr(part, "function_response", None) - span.set_tag_str( - "google_generativeai.request.contents.%d.parts.%d.text" % (content_idx, part_idx), integration.trunc(str(text)) - ) - if function_call: - function_call_dict = type(function_call).to_dict(function_call) - span.set_tag_str( - "google_generativeai.request.contents.%d.parts.%d.function_call.name" % (content_idx, part_idx), - integration.trunc(str(function_call_dict.get("name", ""))), - ) - span.set_tag_str( - "google_generativeai.request.contents.%d.parts.%d.function_call.args" % (content_idx, part_idx), - integration.trunc(str(function_call_dict.get("args", {}))), - ) - if function_response: - function_response_dict = type(function_response).to_dict(function_response) - span.set_tag_str( - "google_generativeai.request.contents.%d.parts.%d.function_response.name" % (content_idx, part_idx), - str(function_response_dict.get("name", "")), - ) - span.set_tag_str( - "google_generativeai.request.contents.%d.parts.%d.function_response.response" % (content_idx, part_idx), - integration.trunc(str(function_response_dict.get("response", {}))), - ) - - def _tag_request_content(span, integration, content, content_idx): """Tag the generation span with request contents.""" if isinstance(content, str): @@ -128,7 +87,7 @@ def _tag_request_content(span, integration, content, content_idx): span.set_tag_str("google_generativeai.request.contents.%d.role" % content_idx, str(content.get("role", ""))) parts = content.get("parts", []) for part_idx, part in enumerate(parts): - _tag_request_content_part(span, integration, part, part_idx, content_idx) + tag_request_content_part_google("google_generativeai", span, integration, part, part_idx, content_idx) return role = getattr(content, "role", "") if role: @@ -141,27 +100,7 @@ def _tag_request_content(span, integration, content, content_idx): ) return for part_idx, part in enumerate(parts): - _tag_request_content_part(span, integration, part, part_idx, content_idx) - - -def _tag_response_part(span, integration, part, part_idx, candidate_idx): - """Tag the generation span with response part text and function calls.""" - text = part.get("text", "") - span.set_tag_str( - "google_generativeai.response.candidates.%d.content.parts.%d.text" % (candidate_idx, part_idx), - integration.trunc(str(text)), - ) - function_call = part.get("function_call", None) - if not function_call: - return - span.set_tag_str( - "google_generativeai.response.candidates.%d.content.parts.%d.function_call.name" % (candidate_idx, part_idx), - integration.trunc(str(function_call.get("name", ""))), - ) - span.set_tag_str( - "google_generativeai.response.candidates.%d.content.parts.%d.function_call.args" % (candidate_idx, part_idx), - integration.trunc(str(function_call.get("args", {}))), - ) + tag_request_content_part_google("google_generativeai", span, integration, part, part_idx, content_idx) def tag_request(span, integration, instance, args, kwargs): @@ -169,7 +108,7 @@ def tag_request(span, integration, instance, args, kwargs): Includes capturing generation configuration, system prompt, and messages. """ contents = get_argument_value(args, kwargs, 0, "contents") - generation_config = kwargs.get("generation_config", {}) + generation_config = get_generation_config_google(instance, kwargs) system_instruction = getattr(instance, "_system_instruction", None) stream = kwargs.get("stream", None) @@ -227,7 +166,7 @@ def tag_response(span, generations, integration, instance): continue parts = candidate_content.get("parts", []) for part_idx, part in enumerate(parts): - _tag_response_part(span, integration, part, part_idx, candidate_idx) + tag_response_part_google("google_generativeai", span, integration, part, part_idx, candidate_idx) token_counts = generations_dict.get("usage_metadata", None) if not token_counts: diff --git a/ddtrace/contrib/internal/google_generativeai/patch.py b/ddtrace/contrib/internal/google_generativeai/patch.py index 2e2c27912eb..29bc18dc756 100644 --- a/ddtrace/contrib/internal/google_generativeai/patch.py +++ b/ddtrace/contrib/internal/google_generativeai/patch.py @@ -7,13 +7,13 @@ from ddtrace.contrib.internal.google_generativeai._utils import TracedAsyncGenerateContentResponse from ddtrace.contrib.internal.google_generativeai._utils import TracedGenerateContentResponse from ddtrace.contrib.internal.google_generativeai._utils import _extract_api_key -from ddtrace.contrib.internal.google_generativeai._utils import _extract_model_name from ddtrace.contrib.internal.google_generativeai._utils import tag_request from ddtrace.contrib.internal.google_generativeai._utils import tag_response from ddtrace.contrib.trace_utils import unwrap from ddtrace.contrib.trace_utils import with_traced_module from ddtrace.contrib.trace_utils import wrap from ddtrace.llmobs._integrations import GeminiIntegration +from ddtrace.llmobs._integrations.utils import extract_model_name_google from ddtrace.pin import Pin @@ -42,7 +42,7 @@ def traced_generate(genai, pin, func, instance, args, kwargs): pin, "%s.%s" % (instance.__class__.__name__, func.__name__), provider="google", - model=_extract_model_name(instance), + model=extract_model_name_google(instance, "model_name"), submit_to_llmobs=True, ) try: @@ -75,7 +75,7 @@ async def traced_agenerate(genai, pin, func, instance, args, kwargs): pin, "%s.%s" % (instance.__class__.__name__, func.__name__), provider="google", - model=_extract_model_name(instance), + model=extract_model_name_google(instance, "model_name"), submit_to_llmobs=True, ) try: diff --git a/ddtrace/contrib/internal/vertexai/_utils.py b/ddtrace/contrib/internal/vertexai/_utils.py new file mode 100644 index 00000000000..07fd0cb69e2 --- /dev/null +++ b/ddtrace/contrib/internal/vertexai/_utils.py @@ -0,0 +1,258 @@ +import sys + +from vertexai.generative_models import GenerativeModel +from vertexai.generative_models import Part + +from ddtrace.internal.utils import get_argument_value +from ddtrace.llmobs._integrations.utils import get_generation_config_google +from ddtrace.llmobs._integrations.utils import tag_request_content_part_google +from ddtrace.llmobs._integrations.utils import tag_response_part_google +from ddtrace.llmobs._utils import _get_attr + + +class BaseTracedVertexAIStreamResponse: + def __init__(self, generator, integration, span, is_chat): + self._generator = generator + self._dd_integration = integration + self._dd_span = span + self._chunks = [] + self.is_chat = is_chat + + +class TracedVertexAIStreamResponse(BaseTracedVertexAIStreamResponse): + def __enter__(self): + self._generator.__enter__() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._generator.__exit__(exc_type, exc_val, exc_tb) + + def __iter__(self): + try: + for chunk in self._generator.__iter__(): + # only keep track of the first chunk for chat messages since + # it is modified during the streaming process + if not self.is_chat or not self._chunks: + self._chunks.append(chunk) + yield chunk + except Exception: + self._dd_span.set_exc_info(*sys.exc_info()) + raise + else: + tag_stream_response(self._dd_span, self._chunks, self._dd_integration) + finally: + self._dd_span.finish() + + +class TracedAsyncVertexAIStreamResponse(BaseTracedVertexAIStreamResponse): + def __aenter__(self): + self._generator.__enter__() + return self + + def __aexit__(self, exc_type, exc_val, exc_tb): + self._generator.__exit__(exc_type, exc_val, exc_tb) + + async def __aiter__(self): + try: + async for chunk in self._generator.__aiter__(): + # only keep track of the first chunk for chat messages since + # it is modified during the streaming process + if not self.is_chat or not self._chunks: + self._chunks.append(chunk) + yield chunk + except Exception: + self._dd_span.set_exc_info(*sys.exc_info()) + raise + else: + tag_stream_response(self._dd_span, self._chunks, self._dd_integration) + finally: + self._dd_span.finish() + + +def get_system_instruction_texts_from_model(instance): + """ + Extract system instructions from model and convert to []str for tagging. + """ + raw_system_instructions = _get_attr(instance, "_system_instruction", []) + if isinstance(raw_system_instructions, str): + return [raw_system_instructions] + elif isinstance(raw_system_instructions, Part): + return [_get_attr(raw_system_instructions, "text", "")] + elif not isinstance(raw_system_instructions, list): + return [] + + system_instructions = [] + for elem in raw_system_instructions: + if isinstance(elem, str): + system_instructions.append(elem) + elif isinstance(elem, Part): + system_instructions.append(_get_attr(elem, "text", "")) + return system_instructions + + +def extract_info_from_parts(parts): + """Return concatenated text from parts and function calls.""" + concatenated_text = "" + function_calls = [] + for part in parts: + text = _get_attr(part, "text", "") + concatenated_text += text + function_call = _get_attr(part, "function_call", None) + if function_call is not None: + function_calls.append(function_call) + return concatenated_text, function_calls + + +def _tag_response_parts(span, integration, parts): + text, function_calls = extract_info_from_parts(parts) + span.set_tag_str( + "vertexai.response.candidates.%d.content.parts.%d.text" % (0, 0), + integration.trunc(str(text)), + ) + for idx, function_call in enumerate(function_calls): + span.set_tag_str( + "vertexai.response.candidates.%d.content.parts.%d.function_calls.%d.function_call.name" % (0, 0, idx), + _get_attr(function_call, "name", ""), + ) + span.set_tag_str( + "vertexai.response.candidates.%d.content.parts.%d.function_calls.%d.function_call.args" % (0, 0, idx), + integration.trunc(str(_get_attr(function_call, "args", ""))), + ) + + +def tag_stream_response(span, chunks, integration): + all_parts = [] + role = "" + for chunk in chunks: + candidates = _get_attr(chunk, "candidates", []) + for candidate_idx, candidate in enumerate(candidates): + finish_reason = _get_attr(candidate, "finish_reason", None) + if finish_reason: + span.set_tag_str( + "vertexai.response.candidates.%d.finish_reason" % (candidate_idx), + _get_attr(finish_reason, "name", ""), + ) + candidate_content = _get_attr(candidate, "content", {}) + role = role or _get_attr(candidate_content, "role", "") + if not integration.is_pc_sampled_span(span): + continue + parts = _get_attr(candidate_content, "parts", []) + all_parts.extend(parts) + token_counts = _get_attr(chunk, "usage_metadata", None) + if not token_counts: + continue + span.set_metric("vertexai.response.usage.prompt_tokens", _get_attr(token_counts, "prompt_token_count", 0)) + span.set_metric( + "vertexai.response.usage.completion_tokens", _get_attr(token_counts, "candidates_token_count", 0) + ) + span.set_metric("vertexai.response.usage.total_tokens", _get_attr(token_counts, "total_token_count", 0)) + # streamed responses have only a single candidate, so there is only one role to be tagged + span.set_tag_str("vertexai.response.candidates.0.content.role", str(role)) + _tag_response_parts(span, integration, all_parts) + + +def _tag_request_content(span, integration, content, content_idx): + """Tag the generation span with request contents.""" + if isinstance(content, str): + span.set_tag_str("vertexai.request.contents.%d.text" % content_idx, integration.trunc(content)) + return + if isinstance(content, dict): + role = content.get("role", "") + if role: + span.set_tag_str("vertexai.request.contents.%d.role" % content_idx, role) + parts = content.get("parts", []) + for part_idx, part in enumerate(parts): + tag_request_content_part_google("vertexai", span, integration, part, part_idx, content_idx) + return + if isinstance(content, Part): + tag_request_content_part_google("vertexai", span, integration, content, 0, content_idx) + return + role = _get_attr(content, "role", "") + if role: + span.set_tag_str("vertexai.request.contents.%d.role" % content_idx, str(role)) + parts = _get_attr(content, "parts", []) + if not parts: + span.set_tag_str( + "vertexai.request.contents.%d.text" % content_idx, + integration.trunc("[Non-text content object: {}]".format(repr(content))), + ) + return + for part_idx, part in enumerate(parts): + tag_request_content_part_google("vertexai", span, integration, part, part_idx, content_idx) + + +def tag_request(span, integration, instance, args, kwargs): + """Tag the generation span with request details. + Includes capturing generation configuration, system prompt, and messages. + """ + # instance is either a chat session or a model itself + model_instance = instance if isinstance(instance, GenerativeModel) else instance._model + contents = get_argument_value(args, kwargs, 0, "contents") + history = _get_attr(instance, "_history", []) + if history: + if isinstance(contents, list): + contents = history + contents + if isinstance(contents, Part) or isinstance(contents, str) or isinstance(contents, dict): + contents = history + [contents] + generation_config = get_generation_config_google(model_instance, kwargs) + generation_config_dict = None + if generation_config is not None: + generation_config_dict = ( + generation_config if isinstance(generation_config, dict) else generation_config.to_dict() + ) + system_instructions = get_system_instruction_texts_from_model(model_instance) + stream = kwargs.get("stream", None) + + if generation_config_dict is not None: + for k, v in generation_config_dict.items(): + span.set_tag_str("vertexai.request.generation_config.%s" % k, str(v)) + + if stream: + span.set_tag("vertexai.request.stream", True) + + if not integration.is_pc_sampled_span(span): + return + + for idx, text in enumerate(system_instructions): + span.set_tag_str( + "vertexai.request.system_instruction.%d.text" % idx, + integration.trunc(str(text)), + ) + + if isinstance(contents, str) or isinstance(contents, dict): + span.set_tag_str("vertexai.request.contents.0.text", integration.trunc(str(contents))) + return + elif isinstance(contents, Part): + tag_request_content_part_google("vertexai", span, integration, contents, 0, 0) + return + elif not isinstance(contents, list): + return + for content_idx, content in enumerate(contents): + _tag_request_content(span, integration, content, content_idx) + + +def tag_response(span, generations, integration): + """Tag the generation span with response details. + Includes capturing generation text, roles, finish reasons, and token counts. + """ + generations_dict = generations.to_dict() + candidates = generations_dict.get("candidates", []) + for candidate_idx, candidate in enumerate(candidates): + finish_reason = _get_attr(candidate, "finish_reason", None) + if finish_reason: + span.set_tag_str("vertexai.response.candidates.%d.finish_reason" % candidate_idx, finish_reason) + candidate_content = _get_attr(candidate, "content", None) + role = _get_attr(candidate_content, "role", "") + span.set_tag_str("vertexai.response.candidates.%d.content.role" % candidate_idx, str(role)) + if not integration.is_pc_sampled_span(span): + continue + parts = _get_attr(candidate_content, "parts", []) + for part_idx, part in enumerate(parts): + tag_response_part_google("vertexai", span, integration, part, part_idx, candidate_idx) + + token_counts = generations_dict.get("usage_metadata", None) + if not token_counts: + return + span.set_metric("vertexai.response.usage.prompt_tokens", _get_attr(token_counts, "prompt_token_count", 0)) + span.set_metric("vertexai.response.usage.completion_tokens", _get_attr(token_counts, "candidates_token_count", 0)) + span.set_metric("vertexai.response.usage.total_tokens", _get_attr(token_counts, "total_token_count", 0)) diff --git a/ddtrace/contrib/internal/vertexai/patch.py b/ddtrace/contrib/internal/vertexai/patch.py new file mode 100644 index 00000000000..1fdfcb7dd16 --- /dev/null +++ b/ddtrace/contrib/internal/vertexai/patch.py @@ -0,0 +1,134 @@ +import os +import sys + +import vertexai + +from ddtrace import config +from ddtrace.contrib.internal.vertexai._utils import TracedAsyncVertexAIStreamResponse +from ddtrace.contrib.internal.vertexai._utils import TracedVertexAIStreamResponse +from ddtrace.contrib.internal.vertexai._utils import tag_request +from ddtrace.contrib.internal.vertexai._utils import tag_response +from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.contrib.trace_utils import wrap +from ddtrace.llmobs._integrations import VertexAIIntegration +from ddtrace.llmobs._integrations.utils import extract_model_name_google +from ddtrace.pin import Pin + + +config._add( + "vertexai", + { + "span_prompt_completion_sample_rate": float(os.getenv("DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE", 1.0)), + "span_char_limit": int(os.getenv("DD_VERTEXAI_SPAN_CHAR_LIMIT", 128)), + }, +) + + +def get_version(): + # type: () -> str + return getattr(vertexai, "__version__", "") + + +@with_traced_module +def traced_generate(vertexai, pin, func, instance, args, kwargs): + return _traced_generate(vertexai, pin, func, instance, args, kwargs, instance, False) + + +@with_traced_module +async def traced_agenerate(vertexai, pin, func, instance, args, kwargs): + return await _traced_agenerate(vertexai, pin, func, instance, args, kwargs, instance, False) + + +@with_traced_module +def traced_send_message(vertexai, pin, func, instance, args, kwargs): + return _traced_generate(vertexai, pin, func, instance, args, kwargs, instance._model, True) + + +@with_traced_module +async def traced_send_message_async(vertexai, pin, func, instance, args, kwargs): + return await _traced_agenerate(vertexai, pin, func, instance, args, kwargs, instance._model, True) + + +def _traced_generate(vertexai, pin, func, instance, args, kwargs, model_instance, is_chat): + integration = vertexai._datadog_integration + stream = kwargs.get("stream", False) + generations = None + span = integration.trace( + pin, + "%s.%s" % (instance.__class__.__name__, func.__name__), + provider="google", + model=extract_model_name_google(model_instance, "_model_name"), + submit_to_llmobs=False, + ) + try: + tag_request(span, integration, instance, args, kwargs) + generations = func(*args, **kwargs) + if stream: + return TracedVertexAIStreamResponse(generations, integration, span, is_chat) + tag_response(span, generations, integration) + except Exception: + span.set_exc_info(*sys.exc_info()) + raise + finally: + # streamed spans will be finished separately once the stream generator is exhausted + if span.error or not stream: + span.finish() + return generations + + +async def _traced_agenerate(vertexai, pin, func, instance, args, kwargs, model_instance, is_chat): + integration = vertexai._datadog_integration + stream = kwargs.get("stream", False) + generations = None + span = integration.trace( + pin, + "%s.%s" % (instance.__class__.__name__, func.__name__), + provider="google", + model=extract_model_name_google(model_instance, "_model_name"), + submit_to_llmobs=False, + ) + try: + tag_request(span, integration, instance, args, kwargs) + generations = await func(*args, **kwargs) + if stream: + return TracedAsyncVertexAIStreamResponse(generations, integration, span, is_chat) + tag_response(span, generations, integration) + except Exception: + span.set_exc_info(*sys.exc_info()) + raise + finally: + # streamed spans will be finished separately once the stream generator is exhausted + if span.error or not stream: + span.finish() + return generations + + +def patch(): + if getattr(vertexai, "_datadog_patch", False): + return + + vertexai._datadog_patch = True + + Pin().onto(vertexai) + integration = VertexAIIntegration(integration_config=config.vertexai) + vertexai._datadog_integration = integration + + wrap("vertexai", "generative_models.GenerativeModel.generate_content", traced_generate(vertexai)) + wrap("vertexai", "generative_models.GenerativeModel.generate_content_async", traced_agenerate(vertexai)) + wrap("vertexai", "generative_models.ChatSession.send_message", traced_send_message(vertexai)) + wrap("vertexai", "generative_models.ChatSession.send_message_async", traced_send_message_async(vertexai)) + + +def unpatch(): + if not getattr(vertexai, "_datadog_patch", False): + return + + vertexai._datadog_patch = False + + unwrap(vertexai.generative_models.GenerativeModel, "generate_content") + unwrap(vertexai.generative_models.GenerativeModel, "generate_content_async") + unwrap(vertexai.generative_models.ChatSession, "send_message") + unwrap(vertexai.generative_models.ChatSession, "send_message_async") + + delattr(vertexai, "_datadog_integration") diff --git a/ddtrace/contrib/vertexai/__init__.py b/ddtrace/contrib/vertexai/__init__.py new file mode 100644 index 00000000000..acc2417b679 --- /dev/null +++ b/ddtrace/contrib/vertexai/__init__.py @@ -0,0 +1,96 @@ +""" +The Vertex AI integration instruments the Vertex Generative AI SDK for Python for requests made to Google models. + +All traces submitted from the Vertex AI integration are tagged by: + +- ``service``, ``env``, ``version``: see the `Unified Service Tagging docs `_. +- ``vertexai.request.provider``: LLM provider used in the request (e.g. ``google`` for Google models). +- ``vertexai.request.model``: Google model used in the request. + +(beta) Prompt and Completion Sampling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prompt texts and completion content are collected in span tags with a default sampling rate of ``1.0`` +for the following methods: + +- ``generate_content/generate_content_async`` of the GenerativeModel class +- ``send_message/send_message_async`` of the ChatSession class + +These tags will have truncation applied if the text exceeds the configured character limit. + + +Enabling +~~~~~~~~ + +The Vertex AI integration is enabled automatically when you use +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Alternatively, use :func:`patch() ` to manually enable the Vertex AI integration:: + + from ddtrace import config, patch + + patch(vertexai=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.vertexai["service"] + + The service name reported by default for Vertex AI requests. + + Alternatively, you can set this option with the ``DD_SERVICE`` or ``DD_VERTEXAI_SERVICE`` environment + variables. + + Default: ``DD_SERVICE`` + + +.. py:data:: (beta) ddtrace.config.vertexai["span_char_limit"] + + Configure the maximum number of characters for the following data within span tags: + + - Text inputs and completions + + Text exceeding the maximum number of characters is truncated to the character limit + and has ``...`` appended to the end. + + Alternatively, you can set this option with the ``DD_VERTEXAI_SPAN_CHAR_LIMIT`` environment + variable. + + Default: ``128`` + + +.. py:data:: (beta) ddtrace.config.vertexai["span_prompt_completion_sample_rate"] + + Configure the sample rate for the collection of prompts and completions as span tags. + + Alternatively, you can set this option with the ``DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``1.0`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the Vertex AI integration on a per-instance basis use the +``Pin`` API:: + + import vertexai + from ddtrace import Pin, config + + Pin.override(vertexai, service="my-vertexai-service") +""" # noqa: E501 + +from ddtrace.internal.utils.importlib import require_modules + + +required_modules = ["vertexai"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from ddtrace.contrib.internal.vertexai.patch import get_version + from ddtrace.contrib.internal.vertexai.patch import patch + from ddtrace.contrib.internal.vertexai.patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/llmobs/_integrations/__init__.py b/ddtrace/llmobs/_integrations/__init__.py index 5303b0530aa..71cae092197 100644 --- a/ddtrace/llmobs/_integrations/__init__.py +++ b/ddtrace/llmobs/_integrations/__init__.py @@ -4,6 +4,7 @@ from .gemini import GeminiIntegration from .langchain import LangChainIntegration from .openai import OpenAIIntegration +from .vertexai import VertexAIIntegration __all__ = [ @@ -13,4 +14,5 @@ "GeminiIntegration", "LangChainIntegration", "OpenAIIntegration", + "VertexAIIntegration", ] diff --git a/ddtrace/llmobs/_integrations/utils.py b/ddtrace/llmobs/_integrations/utils.py new file mode 100644 index 00000000000..695dedb19c8 --- /dev/null +++ b/ddtrace/llmobs/_integrations/utils.py @@ -0,0 +1,74 @@ +from ddtrace.llmobs._utils import _get_attr + + +def extract_model_name_google(instance, model_name_attr): + """Extract the model name from the instance. + Model names are stored in the format `"models/{model_name}"` + so we do our best to return the model name instead of the full string. + """ + model_name = _get_attr(instance, model_name_attr, "") + if not model_name or not isinstance(model_name, str): + return "" + if "/" in model_name: + return model_name.split("/")[-1] + return model_name + + +def get_generation_config_google(instance, kwargs): + """ + The generation config can be defined on the model instance or + as a kwarg of the request. Therefore, try to extract this information + from the kwargs and otherwise default to checking the model instance attribute. + """ + generation_config = kwargs.get("generation_config", {}) + return generation_config or _get_attr(instance, "_generation_config", {}) + + +def tag_request_content_part_google(tag_prefix, span, integration, part, part_idx, content_idx): + """Tag the generation span with request content parts.""" + text = _get_attr(part, "text", "") + function_call = _get_attr(part, "function_call", None) + function_response = _get_attr(part, "function_response", None) + span.set_tag_str( + "%s.request.contents.%d.parts.%d.text" % (tag_prefix, content_idx, part_idx), integration.trunc(str(text)) + ) + if function_call: + function_call_dict = type(function_call).to_dict(function_call) + span.set_tag_str( + "%s.request.contents.%d.parts.%d.function_call.name" % (tag_prefix, content_idx, part_idx), + function_call_dict.get("name", ""), + ) + span.set_tag_str( + "%s.request.contents.%d.parts.%d.function_call.args" % (tag_prefix, content_idx, part_idx), + integration.trunc(str(function_call_dict.get("args", {}))), + ) + if function_response: + function_response_dict = type(function_response).to_dict(function_response) + span.set_tag_str( + "%s.request.contents.%d.parts.%d.function_response.name" % (tag_prefix, content_idx, part_idx), + function_response_dict.get("name", ""), + ) + span.set_tag_str( + "%s.request.contents.%d.parts.%d.function_response.response" % (tag_prefix, content_idx, part_idx), + integration.trunc(str(function_response_dict.get("response", {}))), + ) + + +def tag_response_part_google(tag_prefix, span, integration, part, part_idx, candidate_idx): + """Tag the generation span with response part text and function calls.""" + text = _get_attr(part, "text", "") + span.set_tag_str( + "%s.response.candidates.%d.content.parts.%d.text" % (tag_prefix, candidate_idx, part_idx), + integration.trunc(str(text)), + ) + function_call = _get_attr(part, "function_call", None) + if not function_call: + return + span.set_tag_str( + "%s.response.candidates.%d.content.parts.%d.function_call.name" % (tag_prefix, candidate_idx, part_idx), + _get_attr(function_call, "name", ""), + ) + span.set_tag_str( + "%s.response.candidates.%d.content.parts.%d.function_call.args" % (tag_prefix, candidate_idx, part_idx), + integration.trunc(str(_get_attr(function_call, "args", {}))), + ) diff --git a/ddtrace/llmobs/_integrations/vertexai.py b/ddtrace/llmobs/_integrations/vertexai.py new file mode 100644 index 00000000000..1ad64b61d40 --- /dev/null +++ b/ddtrace/llmobs/_integrations/vertexai.py @@ -0,0 +1,18 @@ +from typing import Any +from typing import Dict +from typing import Optional + +from ddtrace import Span +from ddtrace.llmobs._integrations.base import BaseLLMIntegration + + +class VertexAIIntegration(BaseLLMIntegration): + _integration_name = "vertexai" + + def _set_base_span_tags( + self, span: Span, provider: Optional[str] = None, model: Optional[str] = None, **kwargs: Dict[str, Any] + ) -> None: + if provider is not None: + span.set_tag_str("vertexai.request.provider", provider) + if model is not None: + span.set_tag_str("vertexai.request.model", model) diff --git a/riotfile.py b/riotfile.py index b5414c64c93..8015f035163 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2723,6 +2723,15 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pillow": latest, }, ), + Venv( + name="vertexai", + command="pytest {cmdargs} tests/contrib/vertexai", + pys=select_pys(min_version="3.9"), + pkgs={ + "pytest-asyncio": latest, + "vertexai": [latest], + }, + ), Venv( name="logbook", pys=select_pys(), diff --git a/tests/.suitespec.json b/tests/.suitespec.json index a1200ec4704..8ec302b9f5e 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -291,6 +291,10 @@ "ddtrace/contrib/google_generativeai/*", "ddtrace/contrib/internal/google_generativeai/*" ], + "vertexai": [ + "ddtrace/contrib/vertexai/*", + "ddtrace/contrib/internal/vertexai/*" + ], "subprocess": [ "ddtrace/contrib/subprocess/*", "ddtrace/contrib/internal/subprocess/*" @@ -1505,6 +1509,16 @@ "tests/contrib/google_generativeai/*", "tests/snapshots/tests.contrib.google_generativeai.*" ], + "vertexai": [ + "@bootstrap", + "@core", + "@tracing", + "@contrib", + "@vertexai", + "@llmobs", + "tests/contrib/vertexai/*", + "tests/snapshots/tests.contrib.vertexai.*" + ], "runtime": [ "@core", "@runtime", diff --git a/tests/contrib/vertexai/__init__.py b/tests/contrib/vertexai/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/contrib/vertexai/conftest.py b/tests/contrib/vertexai/conftest.py new file mode 100644 index 00000000000..9f58381ca41 --- /dev/null +++ b/tests/contrib/vertexai/conftest.py @@ -0,0 +1,66 @@ +from mock import PropertyMock +from mock import patch as mock_patch +import pytest + +from ddtrace.contrib.vertexai import patch +from ddtrace.contrib.vertexai import unpatch +from ddtrace.pin import Pin +from tests.contrib.vertexai.utils import MockAsyncPredictionServiceClient +from tests.contrib.vertexai.utils import MockPredictionServiceClient +from tests.utils import DummyTracer +from tests.utils import DummyWriter +from tests.utils import override_config +from tests.utils import override_global_config + + +@pytest.fixture +def ddtrace_global_config(): + return {} + + +@pytest.fixture +def ddtrace_config_vertexai(): + return {} + + +@pytest.fixture +def mock_client(): + yield MockPredictionServiceClient() + + +@pytest.fixture +def mock_async_client(): + yield MockAsyncPredictionServiceClient() + + +@pytest.fixture +def mock_tracer(vertexai): + try: + pin = Pin.get_from(vertexai) + mock_tracer = DummyTracer(writer=DummyWriter(trace_flush_enabled=False)) + pin.override(vertexai, tracer=mock_tracer) + pin.tracer.configure() + yield mock_tracer + except Exception: + yield + + +@pytest.fixture +def vertexai(ddtrace_global_config, ddtrace_config_vertexai, mock_client, mock_async_client): + global_config = ddtrace_global_config + with override_global_config(global_config): + with override_config("vertexai", ddtrace_config_vertexai): + patch() + import vertexai + from vertexai.generative_models import GenerativeModel + + with mock_patch.object( + GenerativeModel, "_prediction_client", new_callable=PropertyMock + ) as mock_client_property, mock_patch.object( + GenerativeModel, "_prediction_async_client", new_callable=PropertyMock + ) as mock_async_client_property: + mock_client_property.return_value = mock_client + mock_async_client_property.return_value = mock_async_client + yield vertexai + + unpatch() diff --git a/tests/contrib/vertexai/test_vertexai.py b/tests/contrib/vertexai/test_vertexai.py new file mode 100644 index 00000000000..5f07c6e177f --- /dev/null +++ b/tests/contrib/vertexai/test_vertexai.py @@ -0,0 +1,535 @@ +import pytest + +from tests.contrib.vertexai.utils import MOCK_COMPLETION_SIMPLE_1 +from tests.contrib.vertexai.utils import MOCK_COMPLETION_SIMPLE_2 +from tests.contrib.vertexai.utils import MOCK_COMPLETION_STREAM_CHUNKS +from tests.contrib.vertexai.utils import MOCK_COMPLETION_TOOL +from tests.contrib.vertexai.utils import MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS +from tests.contrib.vertexai.utils import _async_streamed_response +from tests.contrib.vertexai.utils import _mock_completion_response +from tests.contrib.vertexai.utils import _mock_completion_stream_chunk +from tests.contrib.vertexai.utils import weather_tool +from tests.utils import override_global_config + + +def test_global_tags(vertexai, mock_tracer): + """ + When the global config UST tags are set + The service name should be used for all data + The env should be used for all data + The version should be used for all data + """ + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + with override_global_config(dict(service="test-svc", env="staging", version="1234")): + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert span.resource == "GenerativeModel.generate_content" + assert span.service == "test-svc" + assert span.get_tag("env") == "staging" + assert span.get_tag("version") == "1234" + assert span.get_tag("vertexai.request.model") == "gemini-1.5-flash" + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion") +def test_vertexai_completion(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error", + ignores=["meta.error.stack", "meta.error.message"], +) +def test_vertexai_completion_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + with pytest.raises(TypeError): + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool") +def test_vertexai_completion_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + llm.generate_content( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages") +def test_vertexai_completion_multiple_messages(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + [ + {"role": "user", "parts": [{"text": "Hello World!"}]}, + {"role": "model", "parts": [{"text": "Great to meet you. What would you like to know?"}]}, + {"parts": [{"text": "Why do bears hibernate?"}]}, + ], + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt") +def test_vertexai_completion_system_prompt(vertexai): + llm = vertexai.generative_models.GenerativeModel( + "gemini-1.5-flash", + system_instruction=[ + vertexai.generative_models.Part.from_text("You are required to insist that bears do not hibernate.") + ], + ) + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=50, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream") +def test_vertexai_completion_stream(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + response = llm.generate_content( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error", + ignores=["meta.error.stack", "meta.error.message"], +) +def test_vertexai_completion_stream_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + with pytest.raises(TypeError): + response = llm.generate_content( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, # candidate_count is not a valid keyword argument + ) + for _ in response: + pass + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool") +def test_vertexai_completion_stream_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + response = llm.generate_content( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion", ignores=["resource"]) +async def test_vertexai_completion_async(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + await llm.generate_content_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error", + ignores=["meta.error.stack", "meta.error.message", "resource"], +) +async def test_vertexai_completion_async_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + with pytest.raises(TypeError): + await llm.generate_content_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool", ignores=["resource"]) +async def test_vertexai_completion_async_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + await llm.generate_content_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream", ignores=["resource"] +) +async def test_vertexai_completion_async_stream(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + response = await llm.generate_content_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error", + ignores=["meta.error.stack", "meta.error.message", "resource"], +) +async def test_vertexai_completion_async_stream_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + with pytest.raises(TypeError): + response = await llm.generate_content_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + async for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool", ignores=["resource"] +) +async def test_vertexai_completion_async_stream_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + response = await llm.generate_content_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion", ignores=["resource"]) +def test_vertexai_chat(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat() + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages", ignores=["resource"] +) +def test_vertexai_chat_history(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat( + history=[ + vertexai.generative_models.Content( + role="user", parts=[vertexai.generative_models.Part.from_text("Hello World!")] + ), + vertexai.generative_models.Content( + role="model", + parts=[vertexai.generative_models.Part.from_text("Great to meet you. What would you like to know?")], + ), + ] + ) + chat.send_message( + vertexai.generative_models.Part.from_text("Why do bears hibernate?"), + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error", + ignores=["resource", "meta.error.stack", "meta.error.message"], +) +def test_vertexai_chat_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat() + with pytest.raises(TypeError): + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool", ignores=["resource"]) +def test_vertexai_chat_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + chat = llm.start_chat() + chat.send_message( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt", ignores=["resource"] +) +def test_vertexai_chat_system_prompt(vertexai): + llm = vertexai.generative_models.GenerativeModel( + "gemini-1.5-flash", + system_instruction=[ + vertexai.generative_models.Part.from_text("You are required to insist that bears do not hibernate.") + ], + ) + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) + chat = llm.start_chat() + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=50, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream", ignores=["resource"] +) +def test_vertexai_chat_stream(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = chat.send_message( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error", + ignores=["resource", "meta.error.stack", "meta.error.message"], +) +def test_vertexai_chat_stream_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + with pytest.raises(TypeError): + response = chat.send_message( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool", ignores=["resource"] +) +def test_vertexai_chat_stream_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = chat.send_message( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion", ignores=["resource"]) +async def test_vertexai_chat_async(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + chat = llm.start_chat() + await chat.send_message_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error", + ignores=["resource", "meta.error.stack", "meta.error.message"], +) +async def test_vertexai_chat_async_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + chat = llm.start_chat() + with pytest.raises(TypeError): + await chat.send_message_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, + ) + + +@pytest.mark.snapshot(token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool", ignores=["resource"]) +async def test_vertexai_chat_async_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + chat = llm.start_chat() + await chat.send_message_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream", ignores=["resource"] +) +async def test_vertexai_chat_async_stream(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = await chat.send_message_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error", + ignores=["resource", "meta.error.stack", "meta.error.message"], +) +async def test_vertexai_chat_async_stream_error(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + with pytest.raises(TypeError): + response = await chat.send_message_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + async for _ in response: + pass + + +@pytest.mark.snapshot( + token="tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool", ignores=["resource"] +) +async def test_vertexai_chat_async_stream_tool(vertexai): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = await chat.send_message_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass diff --git a/tests/contrib/vertexai/test_vertexai_patch.py b/tests/contrib/vertexai/test_vertexai_patch.py new file mode 100644 index 00000000000..39ae2d599d1 --- /dev/null +++ b/tests/contrib/vertexai/test_vertexai_patch.py @@ -0,0 +1,30 @@ +from ddtrace.contrib.vertexai import get_version +from ddtrace.contrib.vertexai import patch +from ddtrace.contrib.vertexai import unpatch +from tests.contrib.patch import PatchTestCase + + +class TestVertexAIPatch(PatchTestCase.Base): + __integration_name__ = "vertexai" + __module_name__ = "vertexai" + __patch_func__ = patch + __unpatch_func__ = unpatch + __get_version__ = get_version + + def assert_module_patched(self, vertexai): + self.assert_wrapped(vertexai.generative_models.GenerativeModel.generate_content) + self.assert_wrapped(vertexai.generative_models.GenerativeModel.generate_content_async) + self.assert_wrapped(vertexai.generative_models.ChatSession.send_message) + self.assert_wrapped(vertexai.generative_models.ChatSession.send_message_async) + + def assert_not_module_patched(self, vertexai): + self.assert_not_wrapped(vertexai.generative_models.GenerativeModel.generate_content) + self.assert_not_wrapped(vertexai.generative_models.GenerativeModel.generate_content_async) + self.assert_not_wrapped(vertexai.generative_models.ChatSession.send_message) + self.assert_not_wrapped(vertexai.generative_models.ChatSession.send_message_async) + + def assert_not_module_double_patched(self, vertexai): + self.assert_not_double_wrapped(vertexai.generative_models.GenerativeModel.generate_content) + self.assert_not_double_wrapped(vertexai.generative_models.GenerativeModel.generate_content_async) + self.assert_not_double_wrapped(vertexai.generative_models.ChatSession.send_message) + self.assert_not_double_wrapped(vertexai.generative_models.ChatSession.send_message_async) diff --git a/tests/contrib/vertexai/utils.py b/tests/contrib/vertexai/utils.py new file mode 100644 index 00000000000..bc3eb7c3904 --- /dev/null +++ b/tests/contrib/vertexai/utils.py @@ -0,0 +1,173 @@ +import collections + +from google.cloud import aiplatform_v1 +from vertexai.generative_models import FunctionDeclaration +from vertexai.generative_models import Tool + + +get_current_weather_func = FunctionDeclaration( + name="get_current_weather", + description="Get the current weather in a given location", + parameters={ + "type": "object", + "properties": { + "location": {"type": "string", "description": "The city and state, e.g. San Francisco, CA"}, + "unit": { + "type": "string", + "enum": [ + "celsius", + "fahrenheit", + ], + }, + }, + "required": ["location"], + }, +) + +weather_tool = Tool( + function_declarations=[get_current_weather_func], +) + +MOCK_COMPLETION_SIMPLE_1 = { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Bears hibernate to conserve energy and survive during " + "winter months when food is scarce.\n" + } + ], + "role": "model", + }, + "finish_reason": "STOP", + } + ], + "usage_metadata": {"prompt_token_count": 14, "candidates_token_count": 16, "total_token_count": 30}, +} + +MOCK_COMPLETION_SIMPLE_2 = { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Bears do not hibernate! They enter a state of **deep sleep** " + "during the winter months, but it's not true hibernation. \\n\\nHere's..." + } + ], + "role": "model", + }, + "finish_reason": "STOP", + } + ], + "usage_metadata": {"prompt_token_count": 16, "candidates_token_count": 50, "total_token_count": 66}, +} + +MOCK_COMPLETION_TOOL = { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "function_call": { + "name": "get_current_weather", + "args": { + "location": "New York City, NY", + }, + } + } + ], + }, + "finish_reason": "STOP", + }, + ], + "usage_metadata": { + "prompt_token_count": 43, + "candidates_token_count": 11, + "total_token_count": 54, + }, +} + +MOCK_COMPLETION_STREAM_CHUNKS = ( + {"text": "The"}, + { + "text": " solar system's size is vast, extending from the Sun to the outer reaches", + }, + { + "text": " of the Oort cloud, a distance of roughly 1 to 2 light", + }, + { + "text": "-years.\n", + "finish_reason": "STOP", + "usage_metadata": {"prompt_token_count": 16, "candidates_token_count": 37, "total_token_count": 53}, + }, +) + +MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS = ( + { + "function_call": { + "name": "get_current_weather", + "args": {"location": "New York City, NY"}, + }, + "finish_reason": "STOP", + "usage_metadata": {"prompt_token_count": 43, "candidates_token_count": 11, "total_token_count": 54}, + }, +) + + +async def _async_streamed_response(mock_chunks): + """Return async streamed response chunks to be processed by the mock async client.""" + for chunk in mock_chunks: + yield _mock_completion_stream_chunk(chunk) + + +def _mock_completion_response(mock_completion_dict): + mock_content = aiplatform_v1.Content(mock_completion_dict["candidates"][0]["content"]) + return aiplatform_v1.GenerateContentResponse( + { + "candidates": [ + {"content": mock_content, "finish_reason": mock_completion_dict["candidates"][0]["finish_reason"]} + ], + "usage_metadata": mock_completion_dict["usage_metadata"], + } + ) + + +def _mock_completion_stream_chunk(chunk): + mock_content = None + if chunk.get("text"): + mock_content = aiplatform_v1.Content({"parts": [{"text": chunk["text"]}], "role": "model"}) + elif chunk.get("function_call"): + mock_content = aiplatform_v1.Content({"parts": [{"function_call": chunk["function_call"]}], "role": "model"}) + if chunk.get("finish_reason"): + return aiplatform_v1.GenerateContentResponse( + { + "candidates": [{"content": mock_content, "finish_reason": chunk["finish_reason"]}], + "usage_metadata": chunk["usage_metadata"], + } + ) + return aiplatform_v1.GenerateContentResponse({"candidates": [{"content": mock_content}]}) + + +class MockPredictionServiceClient: + def __init__(self): + self.responses = collections.defaultdict(list) + + def generate_content(self, request, **kwargs): + return self.responses["generate_content"].pop(0) + + def stream_generate_content(self, request, **kwargs): + return self.responses["stream_generate_content"].pop(0) + + +class MockAsyncPredictionServiceClient: + def __init__(self): + self.responses = collections.defaultdict(list) + + async def generate_content(self, request, **kwargs): + return self.responses["generate_content"].pop(0) + + async def stream_generate_content(self, request, **kwargs): + return self.responses["stream_generate_content"].pop(0) diff --git a/tests/llmobs/jobspec.yml b/tests/llmobs/jobspec.yml index f039ce8302c..074e30ea975 100644 --- a/tests/llmobs/jobspec.yml +++ b/tests/llmobs/jobspec.yml @@ -25,3 +25,8 @@ google_generativeai: is_snapshot: true env: SUITE_NAME: google_generativeai +vertexai: + runner: riot + is_snapshot: true + env: + SUITE_NAME: vertexai \ No newline at end of file diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion.json new file mode 100644 index 00000000000..cd3fac9c522 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion.json @@ -0,0 +1,38 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "67378d4d00000000", + "language": "python", + "runtime-id": "4bb0a91fcf15428fa3998e8daa98b1dc", + "vertexai.request.contents.0.text": "Why do bears hibernate?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.response.candidates.0.content.parts.0.text": "Bears hibernate to conserve energy and survive during winter months when food is scarce.\\n", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 87069, + "vertexai.response.usage.completion_tokens": 16, + "vertexai.response.usage.prompt_tokens": 14, + "vertexai.response.usage.total_tokens": 30 + }, + "duration": 338000, + "start": 1731693901811611000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error.json new file mode 100644 index 00000000000..acfeaff92d7 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_error.json @@ -0,0 +1,35 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "67378e4d00000000", + "error.message": "_GenerativeModel.generate_content() got an unexpected keyword argument 'candidate_count'", + "error.stack": "Traceback (most recent call last):\n File \"/Users/nicole.cybul/go/src/github.com/DataDog/dd-trace-py/ddtrace/contrib/internal/vertexai/patch.py\", line 65, in _traced_generate\n generations = func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^\nTypeError: _GenerativeModel.generate_content() got an unexpected keyword argument 'candidate_count'\n", + "error.type": "builtins.TypeError", + "language": "python", + "runtime-id": "2dafbde2c81f4a56b2724cbf793fccff", + "vertexai.request.contents.0.text": "Why do bears hibernate?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 91074 + }, + "duration": 929000, + "start": 1731694157960091000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages.json new file mode 100644 index 00000000000..6797ba9ce81 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_multiple_messages.json @@ -0,0 +1,42 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6737990400000000", + "language": "python", + "runtime-id": "48231302081b43f8a7aafe50c264f22d", + "vertexai.request.contents.0.parts.0.text": "Hello World!", + "vertexai.request.contents.0.role": "user", + "vertexai.request.contents.1.parts.0.text": "Great to meet you. What would you like to know?", + "vertexai.request.contents.1.role": "model", + "vertexai.request.contents.2.parts.0.text": "Why do bears hibernate?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.response.candidates.0.content.parts.0.text": "Bears hibernate to conserve energy and survive during winter months when food is scarce.\\n", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 36364, + "vertexai.response.usage.completion_tokens": 16, + "vertexai.response.usage.prompt_tokens": 14, + "vertexai.response.usage.total_tokens": 30 + }, + "duration": 371000, + "start": 1731696900228980000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream.json new file mode 100644 index 00000000000..4da253a2ad7 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream.json @@ -0,0 +1,39 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "67337ee600000000", + "language": "python", + "runtime-id": "4ccec776d22a4a21b80ec41fbad61a6d", + "vertexai.request.contents.0.text": "How big is the solar system?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.request.stream": "True", + "vertexai.response.candidates.0.content.parts.0.text": "The solar system's size is vast, extending from the Sun to the outer reaches of the Oort cloud, a distance of roughly 1 to 2 lig...", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 43876, + "vertexai.response.usage.completion_tokens": 37, + "vertexai.response.usage.prompt_tokens": 16, + "vertexai.response.usage.total_tokens": 53 + }, + "duration": 2630000, + "start": 1731428070772425000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error.json new file mode 100644 index 00000000000..045bfc4d308 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_error.json @@ -0,0 +1,36 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6732207c00000000", + "error.message": "_GenerativeModel.generate_content() got an unexpected keyword argument 'candidate_count'", + "error.stack": "Traceback (most recent call last):\n File \"/Users/nicole.cybul/go/src/github.com/DataDog/dd-trace-py/ddtrace/contrib/internal/vertexai/patch.py\", line 64, in _traced_generate\n generations = func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^\nTypeError: _GenerativeModel.generate_content() got an unexpected keyword argument 'candidate_count'\n", + "error.type": "builtins.TypeError", + "language": "python", + "runtime-id": "55e7bbd277174a8fa40181f2853449ce", + "vertexai.request.contents.0.text": "How big is the solar system?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.request.stream": "True" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 9240 + }, + "duration": 287000, + "start": 1731338364486529000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool.json new file mode 100644 index 00000000000..98e07395a81 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_stream_tool.json @@ -0,0 +1,41 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "67337ee600000000", + "language": "python", + "runtime-id": "4ccec776d22a4a21b80ec41fbad61a6d", + "vertexai.request.contents.0.text": "What is the weather like in New York City?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.request.stream": "True", + "vertexai.response.candidates.0.content.parts.0.function_calls.0.function_call.args": "{'location': 'New York City, NY'}", + "vertexai.response.candidates.0.content.parts.0.function_calls.0.function_call.name": "get_current_weather", + "vertexai.response.candidates.0.content.parts.0.text": "", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 43876, + "vertexai.response.usage.completion_tokens": 11, + "vertexai.response.usage.prompt_tokens": 43, + "vertexai.response.usage.total_tokens": 54 + }, + "duration": 2963000, + "start": 1731428070809186000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt.json new file mode 100644 index 00000000000..796830bde67 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_system_prompt.json @@ -0,0 +1,39 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "67379a8500000000", + "language": "python", + "runtime-id": "48c95a719e2d4da7970ab997558fba3a", + "vertexai.request.contents.0.text": "Why do bears hibernate?", + "vertexai.request.generation_config.max_output_tokens": "50", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.request.system_instruction.0.text": "You are required to insist that bears do not hibernate.", + "vertexai.response.candidates.0.content.parts.0.text": "Bears do not hibernate! They enter a state of **deep sleep** during the winter months, but it's not true hibernation. \\n\\nHere's...", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 42226, + "vertexai.response.usage.completion_tokens": 50, + "vertexai.response.usage.prompt_tokens": 16, + "vertexai.response.usage.total_tokens": 66 + }, + "duration": 468000, + "start": 1731697285295635000 + }]] diff --git a/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool.json b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool.json new file mode 100644 index 00000000000..0546dac2048 --- /dev/null +++ b/tests/snapshots/tests.contrib.vertexai.test_vertexai.test_vertexai_completion_tool.json @@ -0,0 +1,40 @@ +[[ + { + "name": "vertexai.request", + "service": "tests.contrib.vertexai", + "resource": "GenerativeModel.generate_content", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6737958900000000", + "language": "python", + "runtime-id": "97516bf9fa2346d992a5b650fc1bad60", + "vertexai.request.contents.0.text": "What is the weather like in New York City?", + "vertexai.request.generation_config.max_output_tokens": "30", + "vertexai.request.generation_config.stop_sequences": "['x']", + "vertexai.request.generation_config.temperature": "1.0", + "vertexai.request.model": "gemini-1.5-flash", + "vertexai.request.provider": "google", + "vertexai.response.candidates.0.content.parts.0.function_call.args": "{'location': 'New York City, NY'}", + "vertexai.response.candidates.0.content.parts.0.function_call.name": "get_current_weather", + "vertexai.response.candidates.0.content.parts.0.text": "", + "vertexai.response.candidates.0.content.role": "model", + "vertexai.response.candidates.0.finish_reason": "STOP" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 21574, + "vertexai.response.usage.completion_tokens": 11, + "vertexai.response.usage.prompt_tokens": 43, + "vertexai.response.usage.total_tokens": 54 + }, + "duration": 399000, + "start": 1731696009685675000 + }]] From a664aab0a9d53e8dbb874d68933613de4cbecb1d Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:17:22 -0500 Subject: [PATCH 205/372] chore(ci): run framework tests nightly instead of on PRs (#11492) Given the current state of framework tests (not required for PRs to merge, skipped on main), we've decided to drop these from PRs and have them run nightly instead on `latest` ddtrace. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/test_frameworks.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index bd3abf648bc..4b0124db8d6 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -1,11 +1,19 @@ name: Framework tests on: - push: - branches: - - main - - 'mq-working-branch**' - pull_request: + # DEV: Removing these from PRs for now while + # we investigate the value of framework tests. + # They will run nightly instead, and manual + # workflow dispatch will be enabled + # push: + # branches: + # - main + # - 'mq-working-branch**' + # pull_request: + workflow_dispatch: {} + schedule: + - cron: '0 5 * * *' + concurrency: group: ${{ github.workflow }}-${{ github.ref }} From fa6459b8d46b523575d555f3fb39fb230398ca14 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:30:28 +0000 Subject: [PATCH 206/372] fix(ci_visibility): get default environment from agent when DD_ENV is not set (#11478) This fixes an issue where, if we are in EVP mode, and the agent has a custom default environment set (eg: using `DD_APM_ENV`), but `DD_ENV` is not set in the environment, we would incorrectly set the environment to `None`. Instead, we now query the agent's info page and use the `config.default_env` key to choose the environment value. In somewhat-related changes, we now also explicitly default the environment to `"none"` in agentless mode if it is not set by the user, whereas before we would use a `None` value which would serialize to `null`, and we would rely on the backend to perform the `null` -> `"none"` change. Since the backend was already enforcing this behavior, it should be a no-op from the user's perspective. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/recorder.py | 24 +++- ...et_agent_default_env-bf4a11283dccdf87.yaml | 6 + .../test_ci_visibility_api_client.py | 106 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/ci_visibility-get_agent_default_env-bf4a11283dccdf87.yaml diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index d7f6986566c..d6c2634ed6a 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -222,8 +222,14 @@ def __init__(self, tracer=None, config=None, service=None): self._git_data: GitData = get_git_data_from_tags(self._tags) dd_env = os.getenv("_CI_DD_ENV", ddconfig.env) + dd_env_msg = "" if ddconfig._ci_visibility_agentless_enabled: + # In agentless mode, normalize an unset env to none (this is already done by the backend in most cases, so + # it does not override default behavior) + if dd_env is None: + dd_env = "none" + dd_env_msg = " (not set in environment)" if not self._api_key: raise EnvironmentError( "DD_CIVISIBILITY_AGENTLESS_ENABLED is set, but DD_API_KEY is not set, so ddtrace " @@ -242,6 +248,11 @@ def __init__(self, tracer=None, config=None, service=None): dd_env, ) elif self._agent_evp_proxy_is_available(): + # In EVP-proxy cases, if an env is not provided, we need to get the agent's default env in order to make + # the correct decision: + if dd_env is None: + dd_env = self._agent_get_default_env() + dd_env_msg = " (default environment provided by agent)" self._requests_mode = REQUESTS_MODE.EVP_PROXY_EVENTS requests_mode_str = "EVP Proxy" self._api_client = EVPProxyTestVisibilityAPIClient( @@ -269,7 +280,7 @@ def __init__(self, tracer=None, config=None, service=None): self._configure_writer(coverage_enabled=self._collect_coverage_enabled, url=self.tracer._agent_url) - log.info("Service: %s (env: %s)", self._service, dd_env) + log.info("Service: %s (env: %s%s)", self._service, dd_env, dd_env_msg) log.info("Requests mode: %s", requests_mode_str) log.info("Git metadata upload enabled: %s", self._should_upload_git_metadata) log.info("API-provided settings: coverage collection: %s", self._api_settings.coverage_enabled) @@ -394,6 +405,17 @@ def _agent_evp_proxy_is_available(self): return True return False + def _agent_get_default_env(self): + # type: () -> Optional[str] + try: + info = agent.info(self.tracer._agent_url) + except Exception: + return "none" + + if info: + return info.get("config", {}).get("default_env", "none") + return "none" + @classmethod def is_itr_enabled(cls): # cls.enabled guarantees _instance is not None diff --git a/releasenotes/notes/ci_visibility-get_agent_default_env-bf4a11283dccdf87.yaml b/releasenotes/notes/ci_visibility-get_agent_default_env-bf4a11283dccdf87.yaml new file mode 100644 index 00000000000..d5b1d5c5379 --- /dev/null +++ b/releasenotes/notes/ci_visibility-get_agent_default_env-bf4a11283dccdf87.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + CI Visibility: fixes an issue where the CIVisbility service would incorrectly default the tracer env to ``None`` + in EVP proxy mode if ``DD_ENV`` was not specified but the agent had a default environment set to a value other + than ``none`` (eg: using ``DD_APM_ENV`` in the agent's environment). diff --git a/tests/ci_visibility/api_client/test_ci_visibility_api_client.py b/tests/ci_visibility/api_client/test_ci_visibility_api_client.py index 99bdb0067fe..c261815dcbc 100644 --- a/tests/ci_visibility/api_client/test_ci_visibility_api_client.py +++ b/tests/ci_visibility/api_client/test_ci_visibility_api_client.py @@ -353,6 +353,8 @@ def test_civisibility_api_client_agentless_env_config_success(self, env_vars, ex configurations["custom"] = _expected_config.pop("custom_configurations") if "dd_service" not in _expected_config: _expected_config["dd_service"] = "dd-test-py" + if "dd_env" not in _expected_config: + _expected_config["dd_env"] = "none" git_data = GitData("git@github.com:TestDog/dd-test-py.git", "notmainbranch", "mytestcommitsha1234") with _ci_override_env(_env_vars, full_clear=True), _patch_env_for_testing(): @@ -425,6 +427,7 @@ def test_civisibility_api_client_evp_proxy_config_success(self, env_vars, expect configurations=configurations, git_data=git_data, agent_url="http://patchedagenturl:6218", + dd_env="none", **_expected_config, ) CIVisibility.enable() @@ -435,3 +438,106 @@ def test_civisibility_api_client_evp_proxy_config_success(self, env_vars, expect assert CIVisibility._instance._api_client.__dict__ == expected_client.__dict__ finally: CIVisibility.disable() + + def test_civisibility_api_client_evp_respects_agent_default_config(self): + """Tests that, if no DD_ENV is provided in EVP mode, the agent's default env is used""" + agent_info_response = json.loads( + """ + { + "version": "7.49.1", + "git_commit": "1790cab", + "endpoints": [ + "/v0.3/traces", + "/v0.3/services", + "/v0.4/traces", + "/v0.4/services", + "/v0.5/traces", + "/v0.7/traces", + "/profiling/v1/input", + "/telemetry/proxy/", + "/v0.6/stats", + "/v0.1/pipeline_stats", + "/evp_proxy/v1/", + "/evp_proxy/v2/", + "/evp_proxy/v3/", + "/debugger/v1/input", + "/debugger/v1/diagnostics", + "/symdb/v1/input", + "/dogstatsd/v1/proxy", + "/dogstatsd/v2/proxy", + "/v0.7/config", + "/config/set" + ], + "client_drop_p0s": true, + "span_meta_structs": true, + "long_running_spans": true, + "config": { + "default_env": "not_the_default_default_env", + "target_tps": 10, + "max_eps": 200, + "receiver_port": 8126, + "receiver_socket": "", + "connection_limit": 0, + "receiver_timeout": 0, + "max_request_bytes": 26214400, + "statsd_port": 8125, + "max_memory": 0, + "max_cpu": 0, + "analyzed_spans_by_service": {}, + "obfuscation": { + "elastic_search": true, + "mongo": true, + "sql_exec_plan": false, + "sql_exec_plan_normalize": false, + "http": { + "remove_query_string": false, + "remove_path_digits": false + }, + "remove_stack_traces": false, + "redis": { + "Enabled": true, + "RemoveAllArgs": false + }, + "memcached": { + "Enabled": true, + "KeepCommand": false + } + } + } + } + """ + ) + + configurations = { + "os.architecture": "testarch64", + "os.platform": "Not Actually Linux", + "os.version": "1.2.3-test", + "runtime.name": "CPythonTest", + "runtime.version": "1.2.3", + } + + git_data = GitData("git@github.com:TestDog/dd-test-py.git", "notmainbranch", "mytestcommitsha1234") + with _ci_override_env(full_clear=True), _patch_env_for_testing(), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_is_available", return_value=True + ), mock.patch("ddtrace.internal.agent.info", return_value=agent_info_response), mock.patch( + "ddtrace.internal.agent.get_trace_url", return_value="http://shouldntbeused:6218" + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder.ddtrace.tracer._agent_url", "http://patchedagenturl:6218" + ): + try: + expected_client = EVPProxyTestVisibilityAPIClient( + itr_skipping_level=ITR_SKIPPING_LEVEL.TEST, + configurations=configurations, + git_data=git_data, + agent_url="http://patchedagenturl:6218", + dd_env="not_the_default_default_env", + dd_service="dd-test-py", + ) + CIVisibility.enable() + assert CIVisibility.enabled is True + assert CIVisibility._instance is not None + assert CIVisibility._instance._api_client is not None + + assert CIVisibility._instance._api_client.__dict__ == expected_client.__dict__ + finally: + CIVisibility.disable() From 01e115f39ea062be89c1633fe8adff694f571a3e Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:30:44 +0000 Subject: [PATCH 207/372] chore(ci_visibility): reset tmppath_result_key fixture if need be (#11480) In versions > 7.4 of `pytest`, the `tmppath_result_key` key may be stashed on items, which means it needs to be reinstantiated if we retry it. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_retry_utils.py | 7 +++++++ ddtrace/contrib/pytest/_types.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/ddtrace/contrib/pytest/_retry_utils.py b/ddtrace/contrib/pytest/_retry_utils.py index 4f76d07d68c..de68e7b7c51 100644 --- a/ddtrace/contrib/pytest/_retry_utils.py +++ b/ddtrace/contrib/pytest/_retry_utils.py @@ -8,6 +8,7 @@ from _pytest.runner import CallInfo import pytest +from ddtrace.contrib.pytest._types import tmppath_result_key from ddtrace.contrib.pytest._utils import _TestOutcome from ddtrace.ext.test_visibility.api import TestExcInfo from ddtrace.ext.test_visibility.api import TestStatus @@ -59,6 +60,8 @@ def _get_outcome_from_retry( _outcome_exc_info = TestExcInfo(setup_call.excinfo.type, setup_call.excinfo.value, setup_call.excinfo.tb) item.stash[caplog_records_key] = {} item.stash[caplog_handler_key] = {} + if tmppath_result_key is not None: + item.stash[tmppath_result_key] = {} if setup_report.outcome == outcomes.SKIPPED: _outcome_status = TestStatus.SKIP @@ -71,6 +74,8 @@ def _get_outcome_from_retry( _outcome_exc_info = TestExcInfo(call_call.excinfo.type, call_call.excinfo.value, call_call.excinfo.tb) item.stash[caplog_records_key] = {} item.stash[caplog_handler_key] = {} + if tmppath_result_key is not None: + item.stash[tmppath_result_key] = {} elif call_report.outcome == outcomes.SKIPPED: _outcome_status = TestStatus.SKIP elif call_report.outcome == outcomes.PASSED: @@ -88,6 +93,8 @@ def _get_outcome_from_retry( ) item.stash[caplog_records_key] = {} item.stash[caplog_handler_key] = {} + if tmppath_result_key is not None: + item.stash[tmppath_result_key] = {} item._initrequest() diff --git a/ddtrace/contrib/pytest/_types.py b/ddtrace/contrib/pytest/_types.py index 36f34c82791..ff1d07feb4d 100644 --- a/ddtrace/contrib/pytest/_types.py +++ b/ddtrace/contrib/pytest/_types.py @@ -13,3 +13,8 @@ from _pytest.runner import CallInfo as pytest_CallInfo # noqa: F401 _pytest_report_teststatus_return_type = t.Optional[t.Tuple[str, str, t.Tuple[str, t.Mapping[str, bool]]]] + +if _get_pytest_version_tuple() >= (7, 4, 0): + from _pytest.tmpdir import tmppath_result_key # noqa: F401 +else: + tmppath_result_key = None From a9a6ad712e37e832d0e43a0c9bf6541f7bded8d4 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Fri, 22 Nov 2024 16:43:18 +0100 Subject: [PATCH 208/372] fix(iast): add code to filter out ddtrace stuff from dir() on patched modules (#11490) Signed-off-by: Juanjo Alvarez ## Description While testing on dd-source CI we found an issue where a module was doing a `dir(other_module)` and the changed results from the patched module were breaking stuff (because our patching would add `ddtrace_aspects`, `ddtrace_sink_points`, et cetera to the results and the original module was expecting all `other_module` symbols to have some members like `id`). This PRs fixes this problem by: - Creating a custom `__dir__` function (that will override any pre-existing ones) removing from the results all the symbols that we add ourselves while patching. - Renaming all added `ddtrace` symbols to `__ddtrace`. Also: - Adds a `_DD_IAST_NO_DIR_PATCH` config var to disable the wrapping of the patched module `__dir__` functions in case the user have some side-effect problem. - The return type of `visit_ast` has been fixed (it wrongly was `str` while is in fact a `ast.Module` type). ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_constants.py | 2 + ddtrace/appsec/_iast/_ast/ast_patching.py | 67 ++++++-- ddtrace/appsec/_iast/_ast/visitor.py | 117 ++++++------- ddtrace/appsec/_iast/_loader.py | 10 +- ...e-security-patch-dir-54cc85f18e31f45c.yaml | 4 + tests/appsec/appsec_utils.py | 1 + tests/appsec/iast/_ast/test_ast_patching.py | 155 ++++++++++++++---- .../iast/aspects/test_add_aspect_fixtures.py | 2 +- .../test_add_inplace_aspect_fixtures.py | 2 +- .../ast/other/with_implemented_dir.py | 10 ++ .../ast/other/without_implemented_dir.py | 6 + .../appsec/iast_packages/inside_env_runner.py | 4 +- 12 files changed, 269 insertions(+), 111 deletions(-) create mode 100644 releasenotes/notes/code-security-patch-dir-54cc85f18e31f45c.yaml create mode 100644 tests/appsec/iast/fixtures/ast/other/with_implemented_dir.py create mode 100644 tests/appsec/iast/fixtures/ast/other/without_implemented_dir.py diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index e599bd92a2b..7fefbd6880b 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -134,8 +134,10 @@ class IAST(metaclass=Constant_Class): JSON: Literal["_dd.iast.json"] = "_dd.iast.json" ENABLED: Literal["_dd.iast.enabled"] = "_dd.iast.enabled" PATCH_MODULES: Literal["_DD_IAST_PATCH_MODULES"] = "_DD_IAST_PATCH_MODULES" + ENV_NO_DIR_PATCH: Literal["_DD_IAST_NO_DIR_PATCH"] = "_DD_IAST_NO_DIR_PATCH" DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES" SEP_MODULES: Literal[","] = "," + PATCH_ADDED_SYMBOL_PREFIX: Literal["_ddtrace_"] = "_ddtrace_" METRICS_REPORT_LVLS = ( (TELEMETRY_DEBUG_VERBOSITY, TELEMETRY_DEBUG_NAME), diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 4ae0c31233e..eca157eb3a5 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -5,6 +5,8 @@ import os import re from sys import builtin_module_names +from sys import version_info +import textwrap from types import ModuleType from typing import Optional from typing import Text @@ -14,12 +16,14 @@ from ddtrace.appsec._python_info.stdlib import _stdlib_for_python_version from ddtrace.internal.logger import get_logger from ddtrace.internal.module import origin +from ddtrace.internal.utils.formats import asbool from .visitor import AstVisitor _VISITOR = AstVisitor() +_PREFIX = IAST.PATCH_ADDED_SYMBOL_PREFIX # Prefixes for modules where IAST patching is allowed IAST_ALLOWLIST: Tuple[Text, ...] = ("tests.appsec.iast.",) @@ -278,6 +282,7 @@ "protobuf.", "pycparser.", # this package is called when a module is imported, propagation is not needed "pytest.", # Testing framework + "_pytest.", "setuptools.", "sklearn.", # Machine learning library "sqlalchemy.orm.interfaces.", # Performance optimization @@ -368,7 +373,7 @@ def visit_ast( source_text: Text, module_path: Text, module_name: Text = "", -) -> Optional[str]: +) -> Optional[ast.Module]: parsed_ast = ast.parse(source_text, module_path) _VISITOR.update_location(filename=module_path, module_name=module_name) modified_ast = _VISITOR.visit(parsed_ast) @@ -401,23 +406,56 @@ def _remove_flask_run(text: Text) -> Text: return new_text -def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple[str, str]: +_DIR_WRAPPER = textwrap.dedent( + f""" + + +def {_PREFIX}dir(): + orig_dir = globals().get("{_PREFIX}orig_dir__") + + if orig_dir: + # Use the original __dir__ method and filter the results + results = [name for name in orig_dir() if not name.startswith("{_PREFIX}")] + else: + # List names from the module's __dict__ and filter out the unwanted names + results = [ + name for name in globals() + if not (name.startswith("{_PREFIX}") or name == "__dir__") + ] + + return results + +def {_PREFIX}set_dir_filter(): + if "__dir__" in globals(): + # Store the original __dir__ method + globals()["{_PREFIX}orig_dir__"] = __dir__ + + # Replace the module's __dir__ with the custom one + globals()["__dir__"] = {_PREFIX}dir + +{_PREFIX}set_dir_filter() + + """ +) + + +def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple[str, Optional[ast.Module]]: module_name = module.__name__ module_origin = origin(module) if module_origin is None: log.debug("astpatch_source couldn't find the module: %s", module_name) - return "", "" + return "", None module_path = str(module_origin) try: if module_origin.stat().st_size == 0: # Don't patch empty files like __init__.py log.debug("empty file: %s", module_path) - return "", "" + return "", None except OSError: log.debug("astpatch_source couldn't find the file: %s", module_path, exc_info=True) - return "", "" + return "", None # Get the file extension, if it's dll, os, pyd, dyn, dynlib: return # If its pyc or pyo, change to .py and check that the file exists. If not, @@ -427,30 +465,35 @@ def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple if module_ext.lower() not in {".pyo", ".pyc", ".pyw", ".py"}: # Probably native or built-in module log.debug("extension not supported: %s for: %s", module_ext, module_path) - return "", "" + return "", None with open(module_path, "r", encoding=get_encoding(module_path)) as source_file: try: source_text = source_file.read() except UnicodeDecodeError: log.debug("unicode decode error for file: %s", module_path, exc_info=True) - return "", "" + return "", None if len(source_text.strip()) == 0: # Don't patch empty files like __init__.py log.debug("empty file: %s", module_path) - return "", "" + return "", None if remove_flask_run: source_text = _remove_flask_run(source_text) - new_source = visit_ast( + if not asbool(os.environ.get(IAST.ENV_NO_DIR_PATCH, "false")) and version_info > (3, 7): + # Add the dir filter so __ddtrace stuff is not returned by dir(module) + # does not work in 3.7 because it enters into infinite recursion + source_text += _DIR_WRAPPER + + new_ast = visit_ast( source_text, module_path, module_name=module_name, ) - if new_source is None: + if new_ast is None: log.debug("file not ast patched: %s", module_path) - return "", "" + return "", None - return module_path, new_source + return module_path, new_ast diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index decf62d1384..6283d6cf87b 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -12,6 +12,7 @@ from typing import Text from typing import Tuple # noqa:F401 +from ..._constants import IAST from .._metrics import _set_metric_iast_instrumented_propagation from ..constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS from ..constants import DEFAULT_WEAK_RANDOMNESS_FUNCTIONS @@ -22,11 +23,12 @@ PY38_PLUS = sys.version_info >= (3, 8, 0) PY39_PLUS = sys.version_info >= (3, 9, 0) +_PREFIX = IAST.PATCH_ADDED_SYMBOL_PREFIX CODE_TYPE_FIRST_PARTY = "first_party" CODE_TYPE_DD = "datadog" CODE_TYPE_SITE_PACKAGES = "site_packages" CODE_TYPE_STDLIB = "stdlib" -TAINT_SINK_FUNCTION_REPLACEMENT = "ddtrace_taint_sinks.ast_function" +TAINT_SINK_FUNCTION_REPLACEMENT = _PREFIX + "taint_sinks.ast_function" def _mark_avoid_convert_recursively(node): @@ -38,71 +40,71 @@ def _mark_avoid_convert_recursively(node): _ASPECTS_SPEC: Dict[Text, Any] = { "definitions_module": "ddtrace.appsec._iast._taint_tracking.aspects", - "alias_module": "ddtrace_aspects", + "alias_module": _PREFIX + "aspects", "functions": { - "StringIO": "ddtrace_aspects.stringio_aspect", - "BytesIO": "ddtrace_aspects.bytesio_aspect", - "str": "ddtrace_aspects.str_aspect", - "bytes": "ddtrace_aspects.bytes_aspect", - "bytearray": "ddtrace_aspects.bytearray_aspect", - "ddtrace_iast_flask_patch": "ddtrace_aspects.empty_func", # To avoid recursion + "StringIO": _PREFIX + "aspects.stringio_aspect", + "BytesIO": _PREFIX + "aspects.bytesio_aspect", + "str": _PREFIX + "aspects.str_aspect", + "bytes": _PREFIX + "aspects.bytes_aspect", + "bytearray": _PREFIX + "aspects.bytearray_aspect", + "ddtrace_iast_flask_patch": _PREFIX + "aspects.empty_func", # To avoid recursion }, "stringalike_methods": { - "StringIO": "ddtrace_aspects.stringio_aspect", - "BytesIO": "ddtrace_aspects.bytesio_aspect", - "decode": "ddtrace_aspects.decode_aspect", - "join": "ddtrace_aspects.join_aspect", - "encode": "ddtrace_aspects.encode_aspect", - "extend": "ddtrace_aspects.bytearray_extend_aspect", - "upper": "ddtrace_aspects.upper_aspect", - "lower": "ddtrace_aspects.lower_aspect", - "replace": "ddtrace_aspects.replace_aspect", - "swapcase": "ddtrace_aspects.swapcase_aspect", - "title": "ddtrace_aspects.title_aspect", - "capitalize": "ddtrace_aspects.capitalize_aspect", - "casefold": "ddtrace_aspects.casefold_aspect", - "translate": "ddtrace_aspects.translate_aspect", - "format": "ddtrace_aspects.format_aspect", - "format_map": "ddtrace_aspects.format_map_aspect", - "zfill": "ddtrace_aspects.zfill_aspect", - "ljust": "ddtrace_aspects.ljust_aspect", - "split": "ddtrace_aspects.split_aspect", # Both regular split and re.split - "rsplit": "ddtrace_aspects.rsplit_aspect", - "splitlines": "ddtrace_aspects.splitlines_aspect", + "StringIO": _PREFIX + "aspects.stringio_aspect", + "BytesIO": _PREFIX + "aspects.bytesio_aspect", + "decode": _PREFIX + "aspects.decode_aspect", + "join": _PREFIX + "aspects.join_aspect", + "encode": _PREFIX + "aspects.encode_aspect", + "extend": _PREFIX + "aspects.bytearray_extend_aspect", + "upper": _PREFIX + "aspects.upper_aspect", + "lower": _PREFIX + "aspects.lower_aspect", + "replace": _PREFIX + "aspects.replace_aspect", + "swapcase": _PREFIX + "aspects.swapcase_aspect", + "title": _PREFIX + "aspects.title_aspect", + "capitalize": _PREFIX + "aspects.capitalize_aspect", + "casefold": _PREFIX + "aspects.casefold_aspect", + "translate": _PREFIX + "aspects.translate_aspect", + "format": _PREFIX + "aspects.format_aspect", + "format_map": _PREFIX + "aspects.format_map_aspect", + "zfill": _PREFIX + "aspects.zfill_aspect", + "ljust": _PREFIX + "aspects.ljust_aspect", + "split": _PREFIX + "aspects.split_aspect", # Both regular split and re.split + "rsplit": _PREFIX + "aspects.rsplit_aspect", + "splitlines": _PREFIX + "aspects.splitlines_aspect", # re module and re.Match methods - "findall": "ddtrace_aspects.re_findall_aspect", - "finditer": "ddtrace_aspects.re_finditer_aspect", - "fullmatch": "ddtrace_aspects.re_fullmatch_aspect", - "expand": "ddtrace_aspects.re_expand_aspect", - "group": "ddtrace_aspects.re_group_aspect", - "groups": "ddtrace_aspects.re_groups_aspect", - "match": "ddtrace_aspects.re_match_aspect", - "search": "ddtrace_aspects.re_search_aspect", - "sub": "ddtrace_aspects.re_sub_aspect", - "subn": "ddtrace_aspects.re_subn_aspect", + "findall": _PREFIX + "aspects.re_findall_aspect", + "finditer": _PREFIX + "aspects.re_finditer_aspect", + "fullmatch": _PREFIX + "aspects.re_fullmatch_aspect", + "expand": _PREFIX + "aspects.re_expand_aspect", + "group": _PREFIX + "aspects.re_group_aspect", + "groups": _PREFIX + "aspects.re_groups_aspect", + "match": _PREFIX + "aspects.re_match_aspect", + "search": _PREFIX + "aspects.re_search_aspect", + "sub": _PREFIX + "aspects.re_sub_aspect", + "subn": _PREFIX + "aspects.re_subn_aspect", }, # Replacement function for indexes and ranges "slices": { - "index": "ddtrace_aspects.index_aspect", - "slice": "ddtrace_aspects.slice_aspect", + "index": _PREFIX + "aspects.index_aspect", + "slice": _PREFIX + "aspects.slice_aspect", }, # Replacement functions for modules "module_functions": { "os.path": { - "basename": "ddtrace_aspects.ospathbasename_aspect", - "dirname": "ddtrace_aspects.ospathdirname_aspect", - "join": "ddtrace_aspects.ospathjoin_aspect", - "normcase": "ddtrace_aspects.ospathnormcase_aspect", - "split": "ddtrace_aspects.ospathsplit_aspect", - "splitext": "ddtrace_aspects.ospathsplitext_aspect", + "basename": _PREFIX + "aspects.ospathbasename_aspect", + "dirname": _PREFIX + "aspects.ospathdirname_aspect", + "join": _PREFIX + "aspects.ospathjoin_aspect", + "normcase": _PREFIX + "aspects.ospathnormcase_aspect", + "split": _PREFIX + "aspects.ospathsplit_aspect", + "splitext": _PREFIX + "aspects.ospathsplitext_aspect", } }, "operators": { - ast.Add: "ddtrace_aspects.add_aspect", - "INPLACE_ADD": "ddtrace_aspects.add_inplace_aspect", - "FORMAT_VALUE": "ddtrace_aspects.format_value_aspect", - ast.Mod: "ddtrace_aspects.modulo_aspect", - "BUILD_STRING": "ddtrace_aspects.build_string_aspect", + ast.Add: _PREFIX + "aspects.add_aspect", + "INPLACE_ADD": _PREFIX + "aspects.add_inplace_aspect", + "FORMAT_VALUE": _PREFIX + "aspects.format_value_aspect", + ast.Mod: _PREFIX + "aspects.modulo_aspect", + "BUILD_STRING": _PREFIX + "aspects.build_string_aspect", }, "excluded_from_patching": { # Key: module being patched @@ -123,6 +125,8 @@ def _mark_avoid_convert_recursively(node): }, "django.utils.html": {"": ("format_html", "format_html_join")}, "sqlalchemy.sql.compiler": {"": ("_requires_quotes",)}, + # Our added functions + "": {"": (f"{_PREFIX}dir", f"{_PREFIX}set_dir_filter")}, }, # This is a set since all functions will be replaced by taint_sink_functions "taint_sinks": { @@ -149,10 +153,10 @@ def _mark_avoid_convert_recursively(node): if sys.version_info >= (3, 12): - _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects.ospathsplitroot_aspect" + _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = _PREFIX + "aspects.ospathsplitroot_aspect" if sys.version_info >= (3, 12) or os.name == "nt": - _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects.ospathsplitdrive_aspect" + _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = _PREFIX + "aspects.ospathsplitdrive_aspect" class AstVisitor(ast.NodeTransformer): @@ -163,7 +167,7 @@ def __init__( ): self._sinkpoints_spec = { "definitions_module": "ddtrace.appsec._iast.taint_sinks", - "alias_module": "ddtrace_taint_sinks", + "alias_module": _PREFIX + "taint_sinks", "functions": {}, } self._sinkpoints_functions = self._sinkpoints_spec["functions"] @@ -458,6 +462,9 @@ def visit_FunctionDef(self, def_node: ast.FunctionDef) -> Any: Special case for some tests which would enter in a patching loop otherwise when visiting the check functions """ + if f"{_PREFIX}dir" in def_node.name or f"{_PREFIX}set_dir_filter" in def_node.name: + return def_node + self.replacements_disabled_for_functiondef = def_node.name in self.dont_patch_these_functionsdefs if hasattr(def_node.args, "vararg") and def_node.args.vararg: diff --git a/ddtrace/appsec/_iast/_loader.py b/ddtrace/appsec/_iast/_loader.py index e81024932f6..382060e3b7f 100644 --- a/ddtrace/appsec/_iast/_loader.py +++ b/ddtrace/appsec/_iast/_loader.py @@ -12,19 +12,19 @@ def _exec_iast_patched_module(module_watchdog, module): - patched_source = None + patched_ast = None compiled_code = None if IS_IAST_ENABLED: try: - module_path, patched_source = astpatch_module(module) + module_path, patched_ast = astpatch_module(module) except Exception: log.debug("Unexpected exception while AST patching", exc_info=True) - patched_source = None + patched_ast = None - if patched_source: + if patched_ast: try: # Patched source is compiled in order to execute it - compiled_code = compile(patched_source, module_path, "exec") + compiled_code = compile(patched_ast, module_path, "exec") except Exception: log.debug("Unexpected exception while compiling patched code", exc_info=True) compiled_code = None diff --git a/releasenotes/notes/code-security-patch-dir-54cc85f18e31f45c.yaml b/releasenotes/notes/code-security-patch-dir-54cc85f18e31f45c.yaml new file mode 100644 index 00000000000..a46aadc94b4 --- /dev/null +++ b/releasenotes/notes/code-security-patch-dir-54cc85f18e31f45c.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: patch the module dir function so original pre-patch results are not changed. diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index b7fd5d4743a..cbcc913a7af 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -100,6 +100,7 @@ def appsec_application_server( env[IAST.ENV] = iast_enabled env[IAST.ENV_REQUEST_SAMPLING] = "100" env["_DD_APPSEC_DEDUPLICATION_ENABLED"] = "false" + env[IAST.ENV_NO_DIR_PATCH] = "false" if assert_debug: env["_" + IAST.ENV_DEBUG] = iast_enabled env["_" + IAST.ENV_PROPAGATION_DEBUG] = iast_enabled diff --git a/tests/appsec/iast/_ast/test_ast_patching.py b/tests/appsec/iast/_ast/test_ast_patching.py index fdf9699231e..cf0fabd14e4 100644 --- a/tests/appsec/iast/_ast/test_ast_patching.py +++ b/tests/appsec/iast/_ast/test_ast_patching.py @@ -1,14 +1,21 @@ #!/usr/bin/env python3 import logging +import sys import astunparse import mock import pytest +from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._ast.ast_patching import _in_python_stdlib from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch from ddtrace.appsec._iast._ast.ast_patching import astpatch_module from ddtrace.appsec._iast._ast.ast_patching import visit_ast +from ddtrace.internal.utils.formats import asbool +from tests.utils import override_env + + +_PREFIX = IAST.PATCH_ADDED_SYMBOL_PREFIX @pytest.mark.parametrize( @@ -55,12 +62,12 @@ def test_visit_ast_changed(source_text, module_path, module_name): ], ) def test_astpatch_module_changed(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - "\nimport ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects" + f"\nimport ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks" + f"\nimport ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects" ) assert "ddtrace_aspects.str_aspect(" in new_code @@ -72,12 +79,12 @@ def test_astpatch_module_changed(module_name): ], ) def test_astpatch_module_changed_add_operator(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - "\nimport ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects" + f"\nimport ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks" + f"\nimport ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects" ) assert "ddtrace_aspects.add_aspect(" in new_code @@ -89,12 +96,12 @@ def test_astpatch_module_changed_add_operator(module_name): ], ) def test_astpatch_module_changed_add_inplace_operator(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - "\nimport ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects" + f"\nimport ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks" + f"\nimport ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects" ) assert "ddtrace_aspects.add_inplace_aspect(" in new_code @@ -107,18 +114,18 @@ def test_astpatch_module_changed_add_inplace_operator(module_name): ], ) def test_astpatch_source_changed_with_future_imports(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - """ + f""" '\\nSome\\nmulti-line\\ndocstring\\nhere\\n' from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals -import ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks -import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects +import ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks +import ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects import html""" ) assert "ddtrace_aspects.str_aspect(" in new_code @@ -136,7 +143,7 @@ def test_astpatch_source_changed_with_future_imports(module_name): ], ) def test_astpatch_source_unchanged(module_name): - assert ("", "") == astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) == astpatch_module(__import__(module_name, fromlist=[None])) def test_module_should_iast_patch(): @@ -170,7 +177,9 @@ def test_module_in_python_stdlib(module_name, result): def test_module_path_none(caplog): with caplog.at_level(logging.DEBUG), mock.patch("ddtrace.internal.module.Path.resolve", side_effect=AttributeError): - assert ("", "") == astpatch_module(__import__("tests.appsec.iast.fixtures.ast.str.class_str", fromlist=[None])) + assert ("", None) == astpatch_module( + __import__("tests.appsec.iast.fixtures.ast.str.class_str", fromlist=[None]) + ) assert "astpatch_source couldn't find the module: tests.appsec.iast.fixtures.ast.str.class_str" in caplog.text @@ -182,12 +191,12 @@ def test_module_path_none(caplog): ], ) def test_astpatch_stringio_module_changed(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - "\nimport ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects" + f"\nimport ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks" + f"\nimport ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects" ) assert "ddtrace_aspects.stringio_aspect(" in new_code @@ -200,12 +209,12 @@ def test_astpatch_stringio_module_changed(module_name): ], ) def test_astpatch_bytesio_module_changed(module_name): - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") != (module_path, new_source) - new_code = astunparse.unparse(new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) assert new_code.startswith( - "\nimport ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects" + f"\nimport ddtrace.appsec._iast.taint_sinks as {_PREFIX}taint_sinks" + f"\nimport ddtrace.appsec._iast._taint_tracking.aspects as {_PREFIX}aspects" ) assert "ddtrace_aspects.bytesio_aspect(" in new_code @@ -221,5 +230,81 @@ def test_astpatch_globals_module_unchanged(module_name): This is a regression test for partially matching function names: ``globals()`` was being incorrectly patched with the aspect for ``glob()`` """ - module_path, new_source = astpatch_module(__import__(module_name, fromlist=[None])) - assert ("", "") == (module_path, new_source) + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) == (module_path, new_ast) + + +@pytest.mark.parametrize( + "module_name, env_var", + [ + ("tests.appsec.iast.fixtures.ast.other.with_implemented_dir", "false"), + ("tests.appsec.iast.fixtures.ast.other.with_implemented_dir", "true"), + ("tests.appsec.iast.fixtures.ast.other.without_implemented_dir", "false"), + ("tests.appsec.iast.fixtures.ast.other.without_implemented_dir", "true"), + ], +) +def test_astpatch_dir_patched_with_env_var(module_name, env_var): + """ + Check that the ast_patching._DIR_WRAPPER code is added to the end of the module if + the env var is False and not added otherwise + """ + with override_env({IAST.ENV_NO_DIR_PATCH: env_var}): + module_path, new_ast = astpatch_module(__import__(module_name, fromlist=[None])) + assert ("", None) != (module_path, new_ast) + new_code = astunparse.unparse(new_ast) + + if asbool(env_var): + # If ENV_NO_DIR_PATCH is set to True our added symbols are not filtered out + assert f"{_PREFIX}aspects" in new_code + assert f"{_PREFIX}taint_sinks" in new_code + else: + # Check that the added dir code is there + assert f"def {_PREFIX}dir" in new_code + assert f"def {_PREFIX}set_dir_filter()" in new_code + + +@pytest.mark.parametrize( + "module_name, expected_names", + [ + ( + "tests.appsec.iast.fixtures.ast.other.with_implemented_dir", + {"custom_added", "symbol1", "symbol2", "symbol3", "symbol4"}, + ), + ( + "tests.appsec.iast.fixtures.ast.other.without_implemented_dir", + {"symbol1", "symbol2", "symbol3", "symbol4"}, + ), + ], +) +@pytest.mark.skipif(sys.version_info < (3, 8), reason="the dir wrappers enters and infinite loop in 3.7") +def test_astpatch_dir_patched_with_or_without_custom_dir(module_name, expected_names): + """ + Check that the patched dir doesn't have any __ddtrace symbols and match the original + unpatched dir() output, both with or without a previous custom __dir__ implementation + """ + with override_env({IAST.ENV_NO_DIR_PATCH: "false"}): + imported_mod = __import__(module_name, fromlist=[None]) + orig_dir = set(dir(imported_mod)) + + for name in expected_names: + assert name in orig_dir + + module_path, new_ast = astpatch_module(imported_mod) + assert ("", None) != (module_path, new_ast) + + new_code = astunparse.unparse(new_ast) + assert f"def {_PREFIX}dir" in new_code + assert f"def {_PREFIX}set_dir_filter()" in new_code + + compiled_code = compile(new_ast, module_path, "exec") + exec(compiled_code, imported_mod.__dict__) + patched_dir = set(dir(imported_mod)) + for symbol in patched_dir: + assert not symbol.startswith(_PREFIX) + + # Check that there are no new symbols that were not in the original dir() call + assert len(patched_dir.difference(orig_dir)) == 0 + + # Check that all the symbols in the expected set are in the patched dir() result + for name in expected_names: + assert name in patched_dir diff --git a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py index 1fba28d38c9..19a6a97dae7 100644 --- a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py @@ -38,7 +38,7 @@ def test_operator_add_dis( bytecode = dis.Bytecode(mod.do_operator_add_params) dis.dis(mod.do_operator_add_params) - assert bytecode.codeobj.co_names == ("ddtrace_aspects", "add_aspect") + assert bytecode.codeobj.co_names == ("_ddtrace_aspects", "add_aspect") def test_string_operator_add_one_tainted(self): # type: () -> None string_input = taint_pyobject( diff --git a/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py b/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py index 8c6bf538de4..1d59ba41dbc 100644 --- a/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py @@ -39,7 +39,7 @@ def test_operator_add_inplace_dis( bytecode = dis.Bytecode(mod.do_operator_add_inplace_params) dis.dis(mod.do_operator_add_inplace_params) - assert bytecode.codeobj.co_names == ("ddtrace_aspects", "add_inplace_aspect") + assert bytecode.codeobj.co_names == ("_ddtrace_aspects", "add_inplace_aspect") def test_string_operator_add_inplace_one_tainted(self) -> None: string_input = taint_pyobject( diff --git a/tests/appsec/iast/fixtures/ast/other/with_implemented_dir.py b/tests/appsec/iast/fixtures/ast/other/with_implemented_dir.py new file mode 100644 index 00000000000..e43e0422346 --- /dev/null +++ b/tests/appsec/iast/fixtures/ast/other/with_implemented_dir.py @@ -0,0 +1,10 @@ +symbol1 = "foo" +symbol2 = "bar" +symbol3 = symbol1 + symbol2 +symbol4 = symbol3[1] + +__ddtrace_mark = "baz" + + +def __dir__(): + return ["custom_added"] + [i for i in globals()] diff --git a/tests/appsec/iast/fixtures/ast/other/without_implemented_dir.py b/tests/appsec/iast/fixtures/ast/other/without_implemented_dir.py new file mode 100644 index 00000000000..61cb47bb417 --- /dev/null +++ b/tests/appsec/iast/fixtures/ast/other/without_implemented_dir.py @@ -0,0 +1,6 @@ +symbol1 = "foo" +symbol2 = "bar" +symbol3 = symbol1 + symbol2 +symbol4 = symbol3[1] + +__ddtrace_mark = "baz" diff --git a/tests/appsec/iast_packages/inside_env_runner.py b/tests/appsec/iast_packages/inside_env_runner.py index 530f63152b1..bfd8f6d2eb8 100644 --- a/tests/appsec/iast_packages/inside_env_runner.py +++ b/tests/appsec/iast_packages/inside_env_runner.py @@ -47,8 +47,8 @@ def try_patched(module_name, expect_no_change=False): ), "Patched source is None after patching: Maybe not an error, but something fishy is going on" new_code = unparse(patched_module) assert ( - "import ddtrace.appsec._iast.taint_sinks as ddtrace_taint_sinks" - "\nimport ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects\n" + "import ddtrace.appsec._iast.taint_sinks as _ddtrace_taint_sinks" + "\nimport ddtrace.appsec._iast._taint_tracking.aspects as _ddtrace_aspects\n" ) in new_code, "Patched imports not found" assert "ddtrace_aspects." in new_code, "Patched aspects not found" From b2a7d785c2141ad16ec1a2e56f2cce05d81c4159 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 22 Nov 2024 10:51:33 -0500 Subject: [PATCH 209/372] chore(crashtracker): add profiler_config tag if profiler enabled (#11430) The previous attempt set the tag after crashtracker has started, and tags didn't show up in the logs. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/core/crashtracking.py | 5 ++++ ddtrace/profiling/profiler.py | 25 ++--------------- ddtrace/settings/profiling.py | 22 +++++++++++++++ .../crashtracker/test_crashtracker.py | 28 +++++++++++++++++++ 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/ddtrace/internal/core/crashtracking.py b/ddtrace/internal/core/crashtracking.py index 87bf9881e28..b6c79b336c3 100644 --- a/ddtrace/internal/core/crashtracking.py +++ b/ddtrace/internal/core/crashtracking.py @@ -7,6 +7,8 @@ from ddtrace.internal.runtime import get_runtime_id from ddtrace.internal.runtime import on_runtime_id_change from ddtrace.settings.crashtracker import config as crashtracker_config +from ddtrace.settings.profiling import config as profiling_config +from ddtrace.settings.profiling import config_str is_available: bool = crashtracker.is_available @@ -57,6 +59,9 @@ def start() -> bool: for key, value in crashtracker_config.tags.items(): add_tag(key, value) + if profiling_config.enabled: + add_tag("profiler_config", config_str(profiling_config)) + # Only start if it is enabled if crashtracker_config.enabled: return crashtracker.start() diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index 48789200d7f..309daf7c471 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -16,7 +16,6 @@ from ddtrace.internal import service from ddtrace.internal import uwsgi from ddtrace.internal import writer -from ddtrace.internal.core import crashtracking from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.telemetry import telemetry_writer @@ -31,6 +30,7 @@ from ddtrace.profiling.collector import stack_event from ddtrace.profiling.collector import threading from ddtrace.settings.profiling import config as profiling_config +from ddtrace.settings.profiling import config_str LOG = logging.getLogger(__name__) @@ -204,27 +204,8 @@ def _build_default_exporters(self): self.tags.update({"functionname": self._lambda_function_name}) # Build the list of enabled Profiling features and send along as a tag - configured_features = [] - if self._stack_collector_enabled: - if self._stack_v2_enabled: - configured_features.append("stack_v2") - else: - configured_features.append("stack") - if self._lock_collector_enabled: - configured_features.append("lock") - if self._memory_collector_enabled: - configured_features.append("mem") - if profiling_config.heap.sample_size > 0: - configured_features.append("heap") - - if self._export_libdd_enabled: - configured_features.append("exp_dd") - else: - configured_features.append("exp_py") - configured_features.append("CAP" + str(profiling_config.capture_pct)) - configured_features.append("MAXF" + str(profiling_config.max_frames)) - self.tags.update({"profiler_config": "_".join(configured_features)}) - crashtracking.add_tag("profiler_config", self.tags["profiler_config"]) + profiler_config = config_str(profiling_config) + self.tags.update({"profiler_config": profiler_config}) endpoint_call_counter_span_processor = self.tracer._endpoint_call_counter_span_processor if self.endpoint_collection_enabled: diff --git a/ddtrace/settings/profiling.py b/ddtrace/settings/profiling.py index b77cde1bb3c..ad8d8794d69 100644 --- a/ddtrace/settings/profiling.py +++ b/ddtrace/settings/profiling.py @@ -451,3 +451,25 @@ class ProfilingConfigExport(En): # Enrich tags with git metadata and DD_TAGS config.tags = _enrich_tags(config.tags) + + +def config_str(config): + configured_features = [] + if config.stack.enabled: + if config.stack.v2_enabled: + configured_features.append("stack_v2") + else: + configured_features.append("stack") + if config.lock.enabled: + configured_features.append("lock") + if config.memory.enabled: + configured_features.append("mem") + if config.heap.sample_size > 0: + configured_features.append("heap") + if config.export.libdd_enabled: + configured_features.append("exp_dd") + else: + configured_features.append("exp_py") + configured_features.append("CAP" + str(config.capture_pct)) + configured_features.append("MAXF" + str(config.max_frames)) + return "_".join(configured_features) diff --git a/tests/internal/crashtracker/test_crashtracker.py b/tests/internal/crashtracker/test_crashtracker.py index 049d26e5244..ed338ce95bb 100644 --- a/tests/internal/crashtracker/test_crashtracker.py +++ b/tests/internal/crashtracker/test_crashtracker.py @@ -3,6 +3,8 @@ import pytest +from ddtrace.settings.profiling import config as profiling_config +from ddtrace.settings.profiling import config_str import tests.internal.crashtracker.utils as utils @@ -503,6 +505,32 @@ def test_crashtracker_user_tags_envvar(run_python_code_in_subprocess): assert v.encode() in data +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +def test_crashtracker_set_tag_profiler_config(run_python_code_in_subprocess): + port, sock = utils.crashtracker_receiver_bind() + assert sock + + env = os.environ.copy() + env["DD_TRACE_AGENT_URL"] = "http://localhost:%d" % port + env["DD_PROFILING_ENABLED"] = "1" + stdout, stderr, exitcode, _ = run_python_code_in_subprocess(auto_code, env=env) + + assert not stdout + assert not stderr + assert exitcode == -11 + + # Wait for the connection + conn = utils.listen_get_conn(sock) + assert conn + data = utils.conn_to_bytes(conn) + assert data + + # Now check for the profiler_config tag + assert b"profiler_config" in data + profiler_config = config_str(profiling_config) + assert profiler_config.encode() in data + + @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") @pytest.mark.subprocess() def test_crashtracker_user_tags_profiling(): From 2ddffc980809a82baa10cee5b3633c35e5b97775 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 22 Nov 2024 17:27:29 +0100 Subject: [PATCH 210/372] feat(asm): standalone code security (#11446) --- .github/workflows/system-tests.yml | 5 ++ ddtrace/_trace/tracer.py | 6 +-- ddtrace/appsec/_iast/_iast_request_context.py | 2 - ddtrace/appsec/_iast/taint_sinks/_base.py | 6 +++ ...-security-standalone-0fc5993ded38e83e.yaml | 4 ++ tests/appsec/appsec/test_asm_standalone.py | 20 +++++--- tests/tracer/test_propagation.py | 49 +++++++++++++++---- tests/tracer/test_tracer.py | 17 +++++-- 8 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/feat-code-security-standalone-0fc5993ded38e83e.yaml diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 8d4a6358d75..e692589a702 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -133,6 +133,11 @@ jobs: docker load < images_artifacts/${{ matrix.weblog-variant}}_weblog_${{ github.sha }}.tar.gz docker load < images_artifacts/agent_${{ github.sha }}.tar.gz + # TODO: Enable once https://github.com/DataDog/system-tests/pull/3506 is merged + # - name: Run IAST_STANDALONE + # if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' + # run: ./run.sh IAST_STANDALONE + - name: Run DEFAULT if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'other' run: ./run.sh DEFAULT diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 9d26cb50498..8c82efbdf37 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -233,9 +233,10 @@ def __init__( # _user_sampler is the backup in case we need to revert from remote config to local self._user_sampler: Optional[BaseSampler] = DatadogSampler() self._asm_enabled = asm_config._asm_enabled + self._iast_enabled = asm_config._iast_enabled self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled self._dogstatsd_url = agent.get_stats_url() if dogstatsd_url is None else dogstatsd_url - self._apm_opt_out = self._asm_enabled and self._appsec_standalone_enabled + self._apm_opt_out = (self._asm_enabled or self._iast_enabled) and self._appsec_standalone_enabled if self._apm_opt_out: self.enabled = False # Disable compute stats (neither agent or tracer should compute them) @@ -267,7 +268,6 @@ def __init__( self._partial_flush_min_spans = config._partial_flush_min_spans # Direct link to the appsec processor self._appsec_processor = None - self._iast_enabled = asm_config._iast_enabled self._endpoint_call_counter_span_processor = EndpointCallCounterProcessor() self._span_processors, self._appsec_processor, self._deferred_processors = _default_span_processors_factory( self._filters, @@ -498,7 +498,7 @@ def configure( if appsec_standalone_enabled is not None: self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled = appsec_standalone_enabled - if self._appsec_standalone_enabled and self._asm_enabled: + if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled): self._apm_opt_out = True self.enabled = False # Disable compute stats (neither agent or tracer should compute them) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index b3a01a97cca..f49d2bc59bd 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -18,7 +18,6 @@ from ddtrace.appsec._iast._metrics import _set_span_tag_iast_executed_sink from ddtrace.appsec._iast._metrics import _set_span_tag_iast_request_tainted from ddtrace.appsec._iast.reporter import IastSpanReporter -from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.constants import ORIGIN_KEY from ddtrace.internal import core from ddtrace.internal.logger import get_logger @@ -132,7 +131,6 @@ def _iast_end_request(ctx=None, span=None, *args, **kwargs): if report_data: report_data.build_and_scrub_value_parts() req_span.set_tag_str(IAST.JSON, report_data._to_str()) - _asm_manual_keep(req_span) _set_metric_iast_request_tainted() _set_span_tag_iast_request_tainted(req_span) _set_span_tag_iast_executed_sink(req_span) diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 3aac2a4b170..7db79d33fd8 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -5,6 +5,7 @@ from typing import Text from ddtrace import tracer +from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.cache import LFUCache @@ -90,6 +91,11 @@ def _prepare_report(cls, vulnerability_type, evidence, file_name, line_number): span = tracer.current_root_span() if span: span_id = span.span_id + # Mark the span as kept to avoid being dropped by the agent. + # + # It is important to do it as soon as the vulnerability is reported + # to ensure that any downstream propagation performed has the new priority. + _asm_manual_keep(span) vulnerability = Vulnerability( type=vulnerability_type, diff --git a/releasenotes/notes/feat-code-security-standalone-0fc5993ded38e83e.yaml b/releasenotes/notes/feat-code-security-standalone-0fc5993ded38e83e.yaml new file mode 100644 index 00000000000..495d435dde4 --- /dev/null +++ b/releasenotes/notes/feat-code-security-standalone-0fc5993ded38e83e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Code Security: This introduces "Standalone Code Security", a feature that disables APM in the tracer but keeps Code Security (IAST) enabled. In order to enable it, set the environment variables ``DD_IAST_ENABLED=1`` and ``DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1``. diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py index 23cfd1cad9d..31624724069 100644 --- a/tests/appsec/appsec/test_asm_standalone.py +++ b/tests/appsec/appsec/test_asm_standalone.py @@ -7,19 +7,25 @@ @pytest.fixture( params=[ - {"appsec_enabled": True, "appsec_standalone_enabled": True}, - {"appsec_enabled": True, "appsec_standalone_enabled": False}, - {"appsec_enabled": False, "appsec_standalone_enabled": False}, - {"appsec_enabled": False, "appsec_standalone_enabled": True}, + {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, + {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": False}, + {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": False}, + {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": True}, + {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": True}, + {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": False}, + {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": False}, + {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": True}, {"appsec_enabled": True}, {"appsec_enabled": False}, + {"iast_enabled": True}, + {"iast_enabled": False}, ] ) def tracer_appsec_standalone(request, tracer): tracer.configure(api_version="v0.4", **request.param) yield tracer, request.param # Reset tracer configuration - tracer.configure(api_version="v0.4", appsec_enabled=False, appsec_standalone_enabled=False) + tracer.configure(api_version="v0.4", appsec_enabled=False, appsec_standalone_enabled=False, iast_enabled=False) def test_appsec_standalone_apm_enabled_metric(tracer_appsec_standalone): @@ -27,7 +33,9 @@ def test_appsec_standalone_apm_enabled_metric(tracer_appsec_standalone): with tracer.trace("test", span_type=SpanTypes.WEB) as span: set_http_meta(span, {}, raw_uri="http://example.com/.git", status_code="404") - if args == {"appsec_enabled": True, "appsec_standalone_enabled": True}: + if args.get("appsec_standalone_enabled", None) and ( + args.get("appsec_enabled", None) or args.get("iast_enabled", None) + ): assert span.get_metric("_dd.apm.enabled") == 0.0 else: assert span.get_metric("_dd.apm.enabled") is None diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index e4bf9be6ef3..c97d2ce6b3d 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -317,8 +317,15 @@ def test_extract(tracer): # noqa: F811 assert len(context.get_all_baggage_items()) == 3 -def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation(tracer): # noqa: F811 - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) +def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation( + tracer, appsec_enabled, iast_enabled # noqa: F811 +): + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) try: headers = { "x-datadog-trace-id": "1234", @@ -362,8 +369,15 @@ def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation(t tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) -def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped(tracer): # noqa: F811 - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) +def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped( + tracer, appsec_enabled, iast_enabled # noqa: F811 +): + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) try: with tracer.trace("local_root_span0"): # First span should be kept, as we keep 1 per min @@ -428,10 +442,15 @@ def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_appsec_tag_no_appsec_event_propagation_resets( - tracer, # noqa: F811 + tracer, appsec_enabled, iast_enabled # noqa: F811 ): - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) try: with tracer.trace("local_root_span0"): # First span should be kept, as we keep 1 per min @@ -526,10 +545,15 @@ def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_user_keep( - tracer, upstream_priority # noqa: F811 + tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 ): - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) try: with tracer.trace("local_root_span0"): # First span should be kept, as we keep 1 per min @@ -585,10 +609,15 @@ def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_us @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_appsec_event_present_propagation_force_keep( - tracer, upstream_priority # noqa: F811 + tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 ): - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) try: with tracer.trace("local_root_span0"): # First span should be kept, as we keep 1 per min diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index cd1a2c7cfca..f432403d3f9 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -2043,10 +2043,19 @@ def test_import_ddtrace_tracer_not_module(): assert isinstance(tracer, Tracer) -def test_asm_standalone_configuration(): +@pytest.mark.parametrize("appsec_enabled", [True, False]) +@pytest.mark.parametrize("iast_enabled", [True, False]) +def test_asm_standalone_configuration(appsec_enabled, iast_enabled): + if not appsec_enabled and not iast_enabled: + pytest.skip("AppSec or IAST must be enabled") + tracer = ddtrace.Tracer() - tracer.configure(appsec_enabled=True, appsec_standalone_enabled=True) - assert tracer._asm_enabled is True + tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) + if appsec_enabled: + assert tracer._asm_enabled is True + if iast_enabled: + assert tracer._iast_enabled is True + assert tracer._appsec_standalone_enabled is True assert tracer._apm_opt_out is True assert tracer.enabled is False @@ -2057,7 +2066,7 @@ def test_asm_standalone_configuration(): assert tracer._compute_stats is False # reset tracer values - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False) def test_gc_not_used_on_root_spans(): From 5527990bf8d3ebaf08275547a5efd23991c3c350 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:44:49 +0000 Subject: [PATCH 211/372] feat(ci_visibility): announce beta of new pytest plugin (#11431) This makes a public announcement the beta of the new version of the `pytest` plugin (referred to as `v2`). The release plan is to make this version the default in `ddtrace==3.0.0`, as it includes some breaking changes. The only practical code change being made is renaming the environment variable to something not considered internal, and a pair of deprecation notices. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/tests.yml | 2 +- ddtrace/contrib/pytest/_plugin_v1.py | 8 ++++++++ ddtrace/contrib/pytest/_plugin_v2.py | 11 +++++++++++ ddtrace/contrib/pytest/_utils.py | 2 +- hatch.toml | 2 +- ...-feat-pytest_plugin_beta-f4a607d58d44055c.yaml | 15 +++++++++++++++ riotfile.py | 10 +++++----- tests/contrib/pytest/test_pytest_snapshot_v2.py | 6 +++--- 8 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/ci_visibility-feat-pytest_plugin_beta-f4a607d58d44055c.yaml diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index cc0575ce396..030082260e7 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -4,7 +4,7 @@ stages: variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s REPO_LANG: python # "python" is used everywhere rather than "py" - _DD_CIVISIBILITY_USE_PYTEST_V2: "true" + DD_PYTEST_USE_NEW_PLUGIN_BETA: "true" PYTEST_ADDOPTS: "-s" # CI_DEBUG_SERVICES: "true" diff --git a/ddtrace/contrib/pytest/_plugin_v1.py b/ddtrace/contrib/pytest/_plugin_v1.py index a3a3b504b00..acdc25a594e 100644 --- a/ddtrace/contrib/pytest/_plugin_v1.py +++ b/ddtrace/contrib/pytest/_plugin_v1.py @@ -22,6 +22,7 @@ import pytest import ddtrace +from ddtrace import DDTraceDeprecationWarning from ddtrace.constants import SPAN_KIND from ddtrace.contrib.internal.coverage.data import _coverage_data from ddtrace.contrib.internal.coverage.patch import patch as patch_coverage @@ -70,6 +71,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.inspection import undecorated +from ddtrace.vendor.debtcollector import deprecate log = get_logger(__name__) @@ -446,6 +448,12 @@ def pytest_load_initial_conftests(early_config, parser, args): def pytest_configure(config): + deprecate( + "this version of the pytest ddtrace plugin is slated for deprecation", + message="set DD_PYTEST_USE_NEW_PLUGIN_BETA=true in your environment to preview the next version of the plugin.", + removal_version="3.0.0", + category=DDTraceDeprecationWarning, + ) unpatch_unittest() if is_enabled(config): ddtrace.config.test_visibility._itr_skipping_ignore_parameters = True diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index c8d94ab0b3f..8fff94872c7 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -4,6 +4,7 @@ import pytest +from ddtrace import DDTraceDeprecationWarning from ddtrace import config as dd_config from ddtrace.contrib.coverage import patch as patch_coverage from ddtrace.contrib.internal.coverage.constants import PCT_COVERED_KEY @@ -56,6 +57,7 @@ from ddtrace.internal.test_visibility.api import InternalTestSession from ddtrace.internal.test_visibility.api import InternalTestSuite from ddtrace.internal.test_visibility.coverage_lines import CoverageLines +from ddtrace.vendor.debtcollector import deprecate if _pytest_version_supports_retries(): @@ -175,6 +177,15 @@ def pytest_load_initial_conftests(early_config, parser, args): def pytest_configure(config: pytest_Config) -> None: + # The only way we end up in pytest_configure is if the environment variable is being used, and logging the warning + # now ensures it shows up in output regardless of the use of the -s flag + deprecate( + "the DD_PYTEST_USE_NEW_PLUGIN_BETA environment variable is deprecated", + message="this preview version of the pytest ddtrace plugin will become the only version.", + removal_version="3.0.0", + category=DDTraceDeprecationWarning, + ) + try: if is_enabled(config): unpatch_unittest() diff --git a/ddtrace/contrib/pytest/_utils.py b/ddtrace/contrib/pytest/_utils.py index 500fbc8d8c6..8dc53ab0228 100644 --- a/ddtrace/contrib/pytest/_utils.py +++ b/ddtrace/contrib/pytest/_utils.py @@ -30,7 +30,7 @@ _NODEID_REGEX = re.compile("^(((?P.*)/)?(?P[^/]*?))::(?P.*?)$") -_USE_PLUGIN_V2 = asbool(os.environ.get("_DD_CIVISIBILITY_USE_PYTEST_V2", "false")) +_USE_PLUGIN_V2 = asbool(os.environ.get("DD_PYTEST_USE_NEW_PLUGIN_BETA", "false")) class _PYTEST_STATUS: diff --git a/hatch.toml b/hatch.toml index ba442662416..dddc78a0f8b 100644 --- a/hatch.toml +++ b/hatch.toml @@ -417,7 +417,7 @@ dependencies = [ ] [envs.pytest_plugin_v2.env-vars] -_DD_CIVISIBILITY_USE_PYTEST_V2 = "true" +DD_PYTEST_USE_NEW_PLUGIN_BETA = "true" DD_AGENT_PORT = "9126" [envs.pytest_plugin_v2.scripts] diff --git a/releasenotes/notes/ci_visibility-feat-pytest_plugin_beta-f4a607d58d44055c.yaml b/releasenotes/notes/ci_visibility-feat-pytest_plugin_beta-f4a607d58d44055c.yaml new file mode 100644 index 00000000000..a74fc2c95e3 --- /dev/null +++ b/releasenotes/notes/ci_visibility-feat-pytest_plugin_beta-f4a607d58d44055c.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + CI Visibility: beta release of the new version of the pytest plugin, introducing the following features: + - `Auto Test Retries `_ + - `Early Flake Detection `_ + - Improved coverage collection for `Test Impact Analysis `_ (formerly Intelligent Test Runner) now uses an internal collection method instead of `coverage.py `_, with improved dependency discovery. + + Set the ``DD_PYTEST_USE_NEW_PLUGIN_BETA`` environment variable to ``true`` to use this new version. + + **NOTE:** this new version of the plugin introduces breaking changes: + - ``module``, ``suite``, and ``test`` names are now parsed from the ``item.nodeid`` attribute + - test names now include the class for class-based tests + - Test skipping by Test Impact Analysis (formerly Intelligent Test Runner) is now done at the suite level, instead of at the test level + diff --git a/riotfile.py b/riotfile.py index 8015f035163..d7b9a081269 100644 --- a/riotfile.py +++ b/riotfile.py @@ -102,7 +102,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "DD_INJECT_FORCE": "1", "DD_PATCH_MODULES": "unittest:false", "CMAKE_BUILD_PARALLEL_LEVEL": "12", - "_DD_CIVISIBILITY_USE_PYTEST_V2": "true", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "true", }, venvs=[ Venv( @@ -1606,7 +1606,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, env={ "DD_AGENT_PORT": "9126", - "_DD_CIVISIBILITY_USE_PYTEST_V2": "1", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", }, venvs=[ Venv( @@ -1695,7 +1695,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, env={ - "_DD_CIVISIBILITY_USE_PYTEST_V2": "0", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", }, venvs=[ Venv( @@ -1728,7 +1728,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, env={ - "_DD_CIVISIBILITY_USE_PYTEST_V2": "0", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", }, venvs=[ Venv( @@ -2990,7 +2990,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "DD_PROFILING_ENABLE_ASSERTS": "1", "DD_PROFILING_EXPORT_LIBDD_ENABLED": "1", # Enable pytest v2 plugin to handle pytest-cpp items in the test suite - "_DD_CIVISIBILITY_USE_PYTEST_V2": "1", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", "CPUCOUNT": "12", }, pkgs={ diff --git a/tests/contrib/pytest/test_pytest_snapshot_v2.py b/tests/contrib/pytest/test_pytest_snapshot_v2.py index 4a417c24986..85d70d4c38e 100644 --- a/tests/contrib/pytest/test_pytest_snapshot_v2.py +++ b/tests/contrib/pytest/test_pytest_snapshot_v2.py @@ -85,7 +85,7 @@ def test_add_two_number_list(): DD_PATCH_MODULES="sqlite3:false", CI_PROJECT_DIR=str(self.testdir.tmpdir), DD_CIVISIBILITY_AGENTLESS_ENABLED="false", - _DD_CIVISIBILITY_USE_PYTEST_V2="true", + DD_PYTEST_USE_NEW_PLUGIN_BETA="true", ) ), ) @@ -131,7 +131,7 @@ def test_add_two_number_list(): DD_PATCH_MODULES="sqlite3:false", CI_PROJECT_DIR=str(self.testdir.tmpdir), DD_CIVISIBILITY_AGENTLESS_ENABLED="false", - _DD_CIVISIBILITY_USE_PYTEST_V2="true", + DD_PYTEST_USE_NEW_PLUGIN_BETA="true", ) ), ) @@ -167,7 +167,7 @@ def test_call_urllib(): CI_PROJECT_DIR=str(self.testdir.tmpdir), DD_CIVISIBILITY_AGENTLESS_ENABLED="false", DD_PATCH_MODULES="httpx:true", - _DD_CIVISIBILITY_USE_PYTEST_V2="true", + DD_PYTEST_USE_NEW_PLUGIN_BETA="true", ) ), ) From e758850e32790440a5e0621f6cda84f87746d250 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 22 Nov 2024 12:01:02 -0500 Subject: [PATCH 212/372] chore(ci): update rust min version (#11505) https://github.com/DataDog/dd-trace-py/commit/83ded1333930e17a87f88c2d81efa02401d526e2 bumped rust min version to 1.76.0 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 72213c11868..6b097d46f6b 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,9 @@ LIBDDWAF_VERSION = "1.21.0" -RUST_MINIMUM_VERSION = "1.71" # Safe guess: 1.71 is about a year old as of 2024-07-03 +# DEV: update this accordingly when src/core upgrades libdatadog dependency. +# libdatadog v14.1.0 requires rust 1.76. +RUST_MINIMUM_VERSION = "1.76" # Set macOS SDK default deployment target to 10.14 for C++17 support (if unset, may default to 10.9) if CURRENT_OS == "Darwin": From 4efe1d4b5c703d7767872b7e726f84cc6745d711 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 22 Nov 2024 12:03:56 -0500 Subject: [PATCH 213/372] fix(lib-injection): ensure any user defined sitecustomize.py is loaded after injection (#11469) --- lib-injection/sources/sitecustomize.py | 12 +++++++++--- ...njection-user-sitecustomize-4ebc76e1b722490f.yaml | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-lib-injection-user-sitecustomize-4ebc76e1b722490f.yaml diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 7e4eefb84f5..dbc68f65ebe 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -352,6 +352,15 @@ def _inject(): return else: try: + # Make sure to remove this script's directory, and to add the ddtrace bootstrap directory to the path + # DEV: We need to add the bootstrap directory to the path to ensure the logic to load any user custom + # sitecustomize is preserved. + if SCRIPT_DIR in sys.path: + sys.path.remove(SCRIPT_DIR) + bootstrap_dir = os.path.join(os.path.abspath(os.path.dirname(ddtrace.__file__)), "bootstrap") + if bootstrap_dir not in sys.path: + sys.path.insert(0, bootstrap_dir) + import ddtrace.bootstrap.sitecustomize # Modify the PYTHONPATH for any subprocesses that might be spawned: @@ -363,13 +372,10 @@ def _inject(): if SCRIPT_DIR in python_path: python_path.remove(SCRIPT_DIR) python_path.insert(-1, site_pkgs_path) - bootstrap_dir = os.path.abspath(os.path.dirname(ddtrace.bootstrap.sitecustomize.__file__)) python_path.insert(0, bootstrap_dir) python_path = os.pathsep.join(python_path) os.environ["PYTHONPATH"] = python_path - # Also insert the bootstrap dir in the path of the current python process. - sys.path.insert(0, bootstrap_dir) _log("successfully configured ddtrace package, python path is %r" % os.environ["PYTHONPATH"]) event = gen_telemetry_payload( [ diff --git a/releasenotes/notes/fix-lib-injection-user-sitecustomize-4ebc76e1b722490f.yaml b/releasenotes/notes/fix-lib-injection-user-sitecustomize-4ebc76e1b722490f.yaml new file mode 100644 index 00000000000..de3c3d22451 --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-user-sitecustomize-4ebc76e1b722490f.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: Ensure any user defined ``sitecustomize.py`` are preserved when auto-injecting. From fff9f6c7f02f8cf85e9a91f25e95631cd8aea3da Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Fri, 22 Nov 2024 13:19:48 -0500 Subject: [PATCH 214/372] chore(telemetry): fix namespace on otel metrics (#11506) The namespace for otel metrics should be "tracers" and not "tracer". This regression was caught by system tests: https://github.com/DataDog/system-tests/pull/3497. No additional tests are required. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/_otel_remapper.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ddtrace/settings/_otel_remapper.py b/ddtrace/settings/_otel_remapper.py index 2815dea4cba..8bdb313fdef 100644 --- a/ddtrace/settings/_otel_remapper.py +++ b/ddtrace/settings/_otel_remapper.py @@ -28,6 +28,7 @@ def __class_getitem__(self, item): from ..constants import VERSION_KEY from ..internal.logger import get_logger from ..internal.telemetry import telemetry_writer +from ..internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_TRACER log = get_logger(__name__) @@ -168,7 +169,10 @@ def otel_remapping(): if otel_env.startswith("OTEL_") and otel_env != "OTEL_PYTHON_CONTEXT": log.warning("OpenTelemetry configuration %s is not supported by Datadog.", otel_env) telemetry_writer.add_count_metric( - "tracer", "otel.env.unsupported", 1, (("config_opentelemetry", otel_env.lower()),) + TELEMETRY_NAMESPACE_TAG_TRACER, + "otel.env.unsupported", + 1, + (("config_opentelemetry", otel_env.lower()),), ) continue @@ -181,7 +185,7 @@ def otel_remapping(): otel_value, ) telemetry_writer.add_count_metric( - "tracer", + TELEMETRY_NAMESPACE_TAG_TRACER, "otel.env.hiding", 1, (("config_opentelemetry", otel_env.lower()), ("config_datadog", dd_env.lower())), @@ -201,7 +205,7 @@ def otel_remapping(): otel_value, ) telemetry_writer.add_count_metric( - "tracer", + TELEMETRY_NAMESPACE_TAG_TRACER, "otel.env.invalid", 1, (("config_opentelemetry", otel_env.lower()), ("config_datadog", dd_env.lower())), From 688fa7f096ed1a5774c9d6ef4e31b7dc792db4a2 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:21:28 -0500 Subject: [PATCH 215/372] fix(botocore): ensure streamed bedrock spans finish (#11499) Fixes #11295. There was an issue with the langchain-aws library and our bedrock integration in that langchain-aws' ChatBedrock class was actually resulting in a GeneratorExit because of [this code](https://github.com/langchain-ai/langchain/blob/f173b72e35979b842933774c9c4568c329a0ae8a/libs/core/langchain_core/language_models/chat_models.py#L88-L90) implicitly calling close(stream) under the hood in Python. this means that any [post-processor code](https://github.com/DataDog/dd-trace-py/blob/a664aab0a9d53e8dbb874d68933613de4cbecb1d/ddtrace/contrib/internal/botocore/services/bedrock.py#L84-L94) was actually never reached. Since GeneratorExits do not inherit from Exception (they actually inherit from BaseException), this was not caught in our `except Except` block, meaning spans never went through either of our post-processing (success or error) code. The solution is to move post processing code into a finally block, to ensure that spans will always be finished. Note that GeneratorExits are not indicative of actual user/system errors and will not be flagged as such (spans will not be marked with error, post processing will simply include only information available until the last yielded chunk) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/botocore/services/bedrock.py | 11 +++-- ...ck-early-stream-exit-81da39d97fb1b26e.yaml | 6 +++ tests/contrib/botocore/test_bedrock.py | 39 ++++++++++------ ..._cohere_invoke_stream_multiple_output.json | 44 ------------------- ...e.test_bedrock.test_read_stream_error.json | 39 ---------------- 5 files changed, 39 insertions(+), 100 deletions(-) create mode 100644 releasenotes/notes/fix-bedrock-early-stream-exit-81da39d97fb1b26e.yaml delete mode 100644 tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json delete mode 100644 tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json diff --git a/ddtrace/contrib/internal/botocore/services/bedrock.py b/ddtrace/contrib/internal/botocore/services/bedrock.py index dba11d1d450..7c5f26b07a5 100644 --- a/ddtrace/contrib/internal/botocore/services/bedrock.py +++ b/ddtrace/contrib/internal/botocore/services/bedrock.py @@ -77,10 +77,18 @@ def readlines(self): def __iter__(self): """Wraps around method to tags the response data and finish the span as the user consumes the stream.""" + exception_raised = False try: for line in self.__wrapped__: self._body.append(json.loads(line["chunk"]["bytes"])) yield line + except Exception: + core.dispatch("botocore.patched_bedrock_api_call.exception", [self._execution_ctx, sys.exc_info()]) + exception_raised = True + raise + finally: + if exception_raised: + return metadata = _extract_streamed_response_metadata(self._execution_ctx, self._body) formatted_response = _extract_streamed_response(self._execution_ctx, self._body) model_provider = self._execution_ctx["model_provider"] @@ -92,9 +100,6 @@ def __iter__(self): "botocore.bedrock.process_response", [self._execution_ctx, formatted_response, metadata, self._body, should_set_choice_ids], ) - except Exception: - core.dispatch("botocore.patched_bedrock_api_call.exception", [self._execution_ctx, sys.exc_info()]) - raise def _extract_request_params(params: Dict[str, Any], provider: str) -> Dict[str, Any]: diff --git a/releasenotes/notes/fix-bedrock-early-stream-exit-81da39d97fb1b26e.yaml b/releasenotes/notes/fix-bedrock-early-stream-exit-81da39d97fb1b26e.yaml new file mode 100644 index 00000000000..8447cce8f65 --- /dev/null +++ b/releasenotes/notes/fix-bedrock-early-stream-exit-81da39d97fb1b26e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + botocore: This fix resolves an issue in the Bedrock integration where not consuming the full response stream would prevent spans from finishing. + - | + LLM Observability: This fix ensures bedrock spans are finished even when streamed responses are not fully consumed. diff --git a/tests/contrib/botocore/test_bedrock.py b/tests/contrib/botocore/test_bedrock.py index 3c91b147a97..1001aff0dac 100644 --- a/tests/contrib/botocore/test_bedrock.py +++ b/tests/contrib/botocore/test_bedrock.py @@ -38,6 +38,14 @@ def aws_credentials(): os.environ["AWS_DEFAULT_REGION"] = "us-east-1" +@pytest.fixture +def mock_tracer(ddtrace_global_config, bedrock_client): + pin = Pin.get_from(bedrock_client) + mock_tracer = DummyTracer(writer=DummyWriter(trace_flush_enabled=False)) + pin.override(bedrock_client, tracer=mock_tracer) + yield mock_tracer + + @pytest.fixture def boto3(aws_credentials, mock_llmobs_span_writer, ddtrace_global_config): global_config = {"_dd_api_key": ""} @@ -315,20 +323,6 @@ def test_read_error(bedrock_client, request_vcr): response.get("body").read() -@pytest.mark.snapshot(ignores=["meta.error.stack"]) -def test_read_stream_error(bedrock_client, request_vcr): - body, model = json.dumps(_REQUEST_BODIES["meta"]), _MODELS["meta"] - with request_vcr.use_cassette("meta_invoke_stream.yaml"): - response = bedrock_client.invoke_model_with_response_stream(body=body, modelId=model) - with mock.patch( - "ddtrace.contrib.internal.botocore.services.bedrock._extract_streamed_response" - ) as mock_extract_response: - mock_extract_response.side_effect = Exception("test") - with pytest.raises(Exception): - for _ in response.get("body"): - pass - - @pytest.mark.snapshot(ignores=["meta.error.stack"]) def test_readlines_error(bedrock_client, request_vcr): body, model = json.dumps(_REQUEST_BODIES["meta"]), _MODELS["meta"] @@ -358,3 +352,20 @@ def test_cohere_embedding(bedrock_client, request_vcr): with request_vcr.use_cassette("cohere_embedding.yaml"): response = bedrock_client.invoke_model(body=body, modelId=model) json.loads(response.get("body").read()) + + +def test_span_finishes_after_generator_exit(bedrock_client, request_vcr, mock_tracer): + body, model = json.dumps(_REQUEST_BODIES["anthropic_message"]), _MODELS["anthropic_message"] + with request_vcr.use_cassette("anthropic_message_invoke_stream.yaml"): + response = bedrock_client.invoke_model_with_response_stream(body=body, modelId=model) + i = 0 + with pytest.raises(GeneratorExit): + for _ in response.get("body"): + if i >= 6: + raise GeneratorExit + i += 1 + span = mock_tracer.pop_traces()[0][0] + assert span is not None + assert span.name == "bedrock-runtime.command" + assert span.resource == "InvokeModelWithResponseStream" + assert span.get_tag("bedrock.response.choices.0.text").startswith("Hobb") diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json deleted file mode 100644 index 506f32bb0ab..00000000000 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_cohere_invoke_stream_multiple_output.json +++ /dev/null @@ -1,44 +0,0 @@ -[[ - { - "name": "bedrock-runtime.command", - "service": "aws.bedrock-runtime", - "resource": "InvokeModelWithResponseStream", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.botocore", - "_dd.p.dm": "-0", - "_dd.p.tid": "65a0359000000000", - "bedrock.request.max_tokens": "10", - "bedrock.request.model": "command-light-text-v14", - "bedrock.request.model_provider": "cohere", - "bedrock.request.n": "2", - "bedrock.request.prompt": "\\n\\nHuman: %s\\n\\nAssistant: Can you explain what a LLM chain is?", - "bedrock.request.stop_sequences": "[]", - "bedrock.request.stream": "True", - "bedrock.request.temperature": "0.9", - "bedrock.request.top_k": "0", - "bedrock.request.top_p": "1.0", - "bedrock.response.choices.0.finish_reason": "MAX_TOKENS", - "bedrock.response.choices.0.text": " In machine learning and natural language processing, Long Short", - "bedrock.response.choices.1.finish_reason": "MAX_TOKENS", - "bedrock.response.choices.1.text": " A large language model (LLM) chain refers", - "bedrock.response.duration": "597", - "bedrock.response.id": "2501c28f-0a40-49ec-9b59-5d58313c05f3", - "bedrock.usage.completion_tokens": "20", - "bedrock.usage.prompt_tokens": "40", - "language": "python", - "runtime-id": "e2468c635ce44f8788acce3e9e569237" - }, - "metrics": { - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 21816 - }, - "duration": 980170000, - "start": 1704998288813204000 - }]] diff --git a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json b/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json deleted file mode 100644 index 6a506cd9e88..00000000000 --- a/tests/snapshots/tests.contrib.botocore.test_bedrock.test_read_stream_error.json +++ /dev/null @@ -1,39 +0,0 @@ -[[ - { - "name": "bedrock-runtime.command", - "service": "aws.bedrock-runtime", - "resource": "InvokeModelWithResponseStream", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 1, - "meta": { - "_dd.base_service": "tests.contrib.botocore", - "_dd.p.dm": "-0", - "_dd.p.tid": "659dea8d00000000", - "bedrock.request.max_tokens": "60", - "bedrock.request.model": "llama2-13b-chat-v1", - "bedrock.request.model_provider": "meta", - "bedrock.request.prompt": "What does 'lorem ipsum' mean?", - "bedrock.request.temperature": "0.9", - "bedrock.request.top_p": "1.0", - "bedrock.response.duration": "", - "bedrock.response.id": "4dcc90b7-81b8-4983-b2d3-9989798b0db1", - "bedrock.usage.completion_tokens": "", - "bedrock.usage.prompt_tokens": "", - "error.message": "test", - "error.stack": "Traceback (most recent call last):\n File \"/Users/yun.kim/go/src/github.com/DataDog/dd-trace-py/ddtrace/contrib/botocore/services/bedrock.py\", line 66, in __iter__\n formatted_response = _extract_streamed_response(self._datadog_span, self._body)\n File \"/Users/yun.kim/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3105_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]_botocore_pytest-randomly_vcrpy/lib/python3.10/site-packages/mock/mock.py\", line 1178, in __call__\n return _mock_self._mock_call(*args, **kwargs)\n File \"/Users/yun.kim/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3105_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]_botocore_pytest-randomly_vcrpy/lib/python3.10/site-packages/mock/mock.py\", line 1182, in _mock_call\n return _mock_self._execute_mock_call(*args, **kwargs)\n File \"/Users/yun.kim/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py3105_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_moto[all]_botocore_pytest-randomly_vcrpy/lib/python3.10/site-packages/mock/mock.py\", line 1239, in _execute_mock_call\n raise effect\nException: test\n", - "error.type": "builtins.Exception", - "language": "python", - "runtime-id": "5e97a84a83514f5383132eed9df2755f" - }, - "metrics": { - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 42139 - }, - "duration": 3661000, - "start": 1704848013372717000 - }]] From 9f7551fff839d81084c77b32ecd9a2e1470249c5 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 22 Nov 2024 19:38:03 +0100 Subject: [PATCH 216/372] chore(deprecation): python 3.7 support (#11412) --- ddtrace/__init__.py | 21 ++++++++++++++ ...-deprecation-warning-c6c00bcafda1523f.yaml | 4 +++ tests/integration/test_integration.py | 1 + tests/internal/test_module.py | 29 +++++++++++++++++++ tests/profiling/test_profiler.py | 2 ++ tests/tracer/test_encoders.py | 2 ++ 6 files changed, 59 insertions(+) create mode 100644 releasenotes/notes/python-3.7-deprecation-warning-c6c00bcafda1523f.yaml diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 8ee842a0d89..1954c1961c9 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -1,10 +1,12 @@ import sys import warnings + LOADED_MODULES = frozenset(sys.modules.keys()) from ddtrace.internal.module import ModuleWatchdog + ModuleWatchdog.install() # Ensure we capture references to unpatched modules as early as possible @@ -16,11 +18,13 @@ from .settings import _config as config + # Enable telemetry writer and excepthook as early as possible to ensure we capture any exceptions from initialization import ddtrace.internal.telemetry # noqa: E402 from ._monkey import patch # noqa: E402 from ._monkey import patch_all # noqa: E402 +from .internal.compat import PYTHON_VERSION_INFO # noqa: E402 from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402 from .pin import Pin # noqa: E402 from ddtrace._trace.span import Span # noqa: E402 @@ -28,6 +32,7 @@ from ddtrace.vendor import debtcollector from .version import get_version # noqa: E402 + # DEV: Import deprecated tracer module in order to retain side-effect of package # initialization, which added this module to sys.modules. We catch deprecation # warnings as this is only to retain a side effect of the package @@ -71,3 +76,19 @@ def __getattr__(name): return globals()[name] raise AttributeError("%s has no attribute %s", __name__, name) + + +def check_supported_python_version(): + if PYTHON_VERSION_INFO < (3, 8): + deprecation_message = ( + "Support for ddtrace with Python version %d.%d is deprecated and will be removed in 3.0.0." + ) + if PYTHON_VERSION_INFO < (3, 7): + deprecation_message = "Support for ddtrace with Python version %d.%d was removed in 2.0.0." + debtcollector.deprecate( + (deprecation_message % (PYTHON_VERSION_INFO[0], PYTHON_VERSION_INFO[1])), + category=DDTraceDeprecationWarning, + ) + + +check_supported_python_version() diff --git a/releasenotes/notes/python-3.7-deprecation-warning-c6c00bcafda1523f.yaml b/releasenotes/notes/python-3.7-deprecation-warning-c6c00bcafda1523f.yaml new file mode 100644 index 00000000000..058d626203b --- /dev/null +++ b/releasenotes/notes/python-3.7-deprecation-warning-c6c00bcafda1523f.yaml @@ -0,0 +1,4 @@ +--- +deprecations: + - | + Python 3.7 support is deprecated and will be removed in 3.0 diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index dea74979968..6bf9e80faca 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -772,6 +772,7 @@ def test_logging_during_tracer_init_succeeds_when_debug_logging_and_logs_injecti ), "stderr should not contain any exception logs" +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 deprecation warning") def test_no_warnings_when_Wall(): env = os.environ.copy() # Have to disable sqlite3 as coverage uses it on process shutdown diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index b3cacdc9e17..885f796af81 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -2,11 +2,13 @@ import os from pathlib import Path import sys +import warnings from warnings import warn import mock import pytest +from ddtrace import check_supported_python_version from ddtrace.internal.coverage.code import ModuleCodeCollector from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.module import origin @@ -421,6 +423,7 @@ def ns_hook(module): ModuleWatchdog.uninstall() +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 deprecation warning") @pytest.mark.subprocess( ddtrace_run=True, env=dict( @@ -588,3 +591,29 @@ def __getattr__(name): "ddtrace.contrib.pytest_bdd.plugin", ] ) + + +@pytest.mark.skipif(sys.version_info >= (3, 8), reason="Python >= 3.8 is supported") +def test_deprecated_python_version(): + # Test that the deprecation warning for Python 3.7 and below is printed in unsupported Python versions. + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + check_supported_python_version() + # Verify some things + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "Support for ddtrace with Python version" in str(w[-1].message) + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python < 3.8 is unsupported") +def test_non_deprecated_python_version(): + # Test that the deprecation warning for Python 3.7 and below is not printed in supported Python versions. + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + check_supported_python_version() + # Verify some things + assert len(w) == 0 diff --git a/tests/profiling/test_profiler.py b/tests/profiling/test_profiler.py index a40ec3ae6e6..8bb092bfef6 100644 --- a/tests/profiling/test_profiler.py +++ b/tests/profiling/test_profiler.py @@ -1,5 +1,6 @@ import logging import os +import sys import time import mock @@ -422,6 +423,7 @@ def test_profiler_serverless(monkeypatch): assert p.tags["functionname"] == "foobar" +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 deprecation warning") @pytest.mark.subprocess() def test_profiler_ddtrace_deprecation(): """ diff --git a/tests/tracer/test_encoders.py b/tests/tracer/test_encoders.py index 213027a7103..f96e063502e 100644 --- a/tests/tracer/test_encoders.py +++ b/tests/tracer/test_encoders.py @@ -3,6 +3,7 @@ import json import random import string +import sys import threading from unittest import TestCase @@ -871,6 +872,7 @@ def test_json_encoder_traces_bytes(): assert "\x80span.b" == span_c["name"] +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 deprecation warning") @pytest.mark.subprocess(env={"DD_TRACE_API_VERSION": "v0.3"}) def test_v03_trace_api_deprecation(): import warnings From defea4e21302a5d452919363403f06995b3992f9 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 22 Nov 2024 14:10:18 -0500 Subject: [PATCH 217/372] feat(profiling): support dynamic agent url (#11319) https://ddtrace.readthedocs.io/en/stable/advanced_usage.html#agent-configuration Customers can use `tracer.configure()` to set the agent url on the fly, and profiler doesn't use the url passed in. Without this change, the following test fails with the Python exporter having `http://localhost:8126` as endpoint url. ``` def test_tracer_url_configure_after(): t = ddtrace.Tracer() prof = profiler.Profiler(tracer=t) t.configure(hostname="foobar") _check_url(prof, "http://foobar:8126", os.environ.get("DD_API_KEY")) ``` Addressing customer issue [PROF-10698](https://datadoghq.atlassian.net/browse/PROF-10698) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) [PROF-10698]: https://datadoghq.atlassian.net/browse/PROF-10698?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../internal/datadog/profiling/ddup/_ddup.pyi | 1 - .../internal/datadog/profiling/ddup/_ddup.pyx | 17 ++- ddtrace/profiling/exporter/http.py | 16 ++- ddtrace/profiling/profiler.py | 34 +---- ddtrace/profiling/utils.py | 29 +++++ ...ng-dynamic-agent-url-1f285029201620b8.yaml | 4 + tests/profiling/exporter/test_http.py | 122 ++++++++++-------- tests/profiling/test_profiler.py | 31 +---- tests/profiling_v2/exporter/test_ddup.py | 1 - 9 files changed, 138 insertions(+), 117 deletions(-) create mode 100644 ddtrace/profiling/utils.py create mode 100644 releasenotes/notes/profiling-dynamic-agent-url-1f285029201620b8.yaml diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi index ab5ad1adb81..552e377df0b 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi @@ -10,7 +10,6 @@ def config( version: StringType, tags: Optional[Dict[Union[str, bytes], Union[str, bytes]]], max_nframes: Optional[int], - url: Optional[str], timeline_enabled: Optional[bool], output_filename: Optional[str], sample_pool_capacity: Optional[int], diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx index 4b4cc6551ce..b3f9b264890 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx @@ -15,6 +15,7 @@ import ddtrace import platform from .._types import StringType from ..util import sanitize_string +from ddtrace.internal import agent from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.packages import get_distributions from ddtrace.internal.runtime import get_runtime_id @@ -339,7 +340,6 @@ def config( version: StringType = None, tags: Optional[Dict[Union[str, bytes], Union[str, bytes]]] = None, max_nframes: Optional[int] = None, - url: StringType = None, timeline_enabled: Optional[bool] = None, output_filename: StringType = None, sample_pool_capacity: Optional[int] = None, @@ -354,8 +354,6 @@ def config( call_func_with_str(ddup_config_env, env) if version: call_func_with_str(ddup_config_version, version) - if url: - call_func_with_str(ddup_config_url, url) if output_filename: call_func_with_str(ddup_config_output_filename, output_filename) @@ -388,6 +386,16 @@ def start() -> None: ddup_start() +def _get_endpoint(tracer)-> str: + # DEV: ddtrace.profiling.utils has _get_endpoint but importing that function + # leads to a circular import, so re-implementing it here. + # TODO(taegyunkim): support agentless mode by modifying uploader_builder to + # build exporter for agentless mode too. + tracer_agent_url = tracer.agent_trace_url + endpoint = tracer_agent_url if tracer_agent_url else agent.get_trace_url() + return endpoint + + def upload() -> None: call_func_with_str(ddup_set_runtime_id, get_runtime_id()) @@ -397,6 +405,9 @@ def upload() -> None: call_ddup_profile_set_endpoints(endpoint_to_span_ids) call_ddup_profile_add_endpoint_counts(endpoint_counts) + endpoint = _get_endpoint(ddtrace.tracer) + call_func_with_str(ddup_config_url, endpoint) + with nogil: ddup_upload() diff --git a/ddtrace/profiling/exporter/http.py b/ddtrace/profiling/exporter/http.py index eb1b2f8221c..27dcf9be87b 100644 --- a/ddtrace/profiling/exporter/http.py +++ b/ddtrace/profiling/exporter/http.py @@ -20,6 +20,8 @@ from ddtrace.profiling import exporter from ddtrace.profiling import recorder # noqa:F401 from ddtrace.profiling.exporter import pprof +from ddtrace.profiling.utils import _get_endpoint +from ddtrace.profiling.utils import _get_endpoint_path from ddtrace.settings.profiling import config @@ -39,8 +41,8 @@ class PprofHTTPExporter(pprof.PprofExporter): def __init__( self, + tracer: ddtrace.Tracer = ddtrace.tracer, enable_code_provenance: bool = True, - endpoint: typing.Optional[str] = None, api_key: typing.Optional[str] = None, timeout: float = config.api_timeout, service: typing.Optional[str] = None, @@ -48,15 +50,14 @@ def __init__( version: typing.Optional[str] = None, tags: typing.Optional[typing.Dict[str, str]] = None, max_retry_delay: typing.Optional[float] = None, - endpoint_path: str = "/profiling/v1/input", endpoint_call_counter_span_processor: typing.Optional[EndpointCallCounterProcessor] = None, *args, **kwargs, ): super().__init__(*args, **kwargs) # repeat this to please mypy + self.tracer = tracer self.enable_code_provenance: bool = enable_code_provenance - self.endpoint: str = endpoint if endpoint is not None else agent.get_trace_url() self.api_key: typing.Optional[str] = api_key # Do not use the default agent timeout: it is too short, the agent is just a unbuffered proxy and the profiling # backend is not as fast as the tracer one. @@ -67,13 +68,20 @@ def __init__( self.tags: typing.Dict[str, str] = tags if tags is not None else {} self.max_retry_delay: typing.Optional[float] = max_retry_delay self._container_info: typing.Optional[container.CGroupInfo] = container.get_container_info() - self.endpoint_path: str = endpoint_path self.endpoint_call_counter_span_processor: typing.Optional[ EndpointCallCounterProcessor ] = endpoint_call_counter_span_processor self.__post_init__() + @property + def endpoint(self): + return _get_endpoint(self.tracer, agentless=config.agentless) + + @property + def endpoint_path(self): + return _get_endpoint_path(agentless=config.agentless) + def __eq__(self, other): # Exporter class used to be decorated with @attr.s which implements __eq__, using only the attributes defined # in the class. However, the _upload attribute is added dynamically to the class, so we need to ignore it when diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index 309daf7c471..fa4bdb79a3e 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -10,12 +10,10 @@ import ddtrace from ddtrace import config -from ddtrace.internal import agent from ddtrace.internal import atexit from ddtrace.internal import forksafe from ddtrace.internal import service from ddtrace.internal import uwsgi -from ddtrace.internal import writer from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.telemetry import telemetry_writer @@ -110,18 +108,14 @@ class _ProfilerInstance(service.Service): """ - ENDPOINT_TEMPLATE = "https://intake.profile.{}" - def __init__( self, - url: Optional[str] = None, service: Optional[str] = None, tags: Optional[Dict[str, str]] = None, env: Optional[str] = None, version: Optional[str] = None, tracer: Any = ddtrace.tracer, api_key: Optional[str] = None, - agentless: bool = profiling_config.agentless, _memory_collector_enabled: bool = profiling_config.memory.enabled, _stack_collector_enabled: bool = profiling_config.stack.enabled, _stack_v2_enabled: bool = profiling_config.stack.v2_enabled, @@ -131,14 +125,12 @@ def __init__( ): super().__init__() # User-supplied values - self.url: Optional[str] = url self.service: Optional[str] = service if service is not None else config.service self.tags: Dict[str, str] = tags if tags is not None else profiling_config.tags self.env: Optional[str] = env if env is not None else config.env self.version: Optional[str] = version if version is not None else config.version self.tracer: Any = tracer self.api_key: Optional[str] = api_key if api_key is not None else config._dd_api_key - self.agentless: bool = agentless self._memory_collector_enabled: bool = _memory_collector_enabled self._stack_collector_enabled: bool = _stack_collector_enabled self._stack_v2_enabled: bool = _stack_v2_enabled @@ -178,28 +170,6 @@ def _build_default_exporters(self): file.PprofFileExporter(prefix=_OUTPUT_PPROF), ] - if self.url is not None: - endpoint = self.url - elif self.agentless: - LOG.warning( - "Agentless uploading is currently for internal usage only and not officially supported. " - "You should not enable it unless somebody at Datadog instructed you to do so." - ) - endpoint = self.ENDPOINT_TEMPLATE.format(os.environ.get("DD_SITE", "datadoghq.com")) - else: - if isinstance(self.tracer._writer, writer.AgentWriter): - endpoint = self.tracer._writer.agent_url - else: - endpoint = agent.get_trace_url() - - if self.agentless: - endpoint_path = "/api/v2/profile" - else: - # Agent mode - # path is relative because it is appended - # to the agent base path. - endpoint_path = "profiling/v1/input" - if self._lambda_function_name is not None: self.tags.update({"functionname": self._lambda_function_name}) @@ -221,7 +191,6 @@ def _build_default_exporters(self): version=self.version, tags=self.tags, # type: ignore max_nframes=profiling_config.max_frames, - url=endpoint, timeline_enabled=profiling_config.timeline_enabled, output_filename=profiling_config.output_pprof, sample_pool_capacity=profiling_config.sample_pool_capacity, @@ -256,13 +225,12 @@ def _build_default_exporters(self): return [ http.PprofHTTPExporter( + tracer=self.tracer, service=self.service, env=self.env, tags=self.tags, version=self.version, api_key=self.api_key, - endpoint=endpoint, - endpoint_path=endpoint_path, enable_code_provenance=self.enable_code_provenance, endpoint_call_counter_span_processor=endpoint_call_counter_span_processor, ) diff --git a/ddtrace/profiling/utils.py b/ddtrace/profiling/utils.py new file mode 100644 index 00000000000..dcfa480de6e --- /dev/null +++ b/ddtrace/profiling/utils.py @@ -0,0 +1,29 @@ +import logging +import os + +from ddtrace.internal import agent + + +LOG = logging.getLogger(__name__) + + +def _get_endpoint(tracer, agentless=False) -> str: + if agentless: + LOG.warning( + "Agentless uploading is currently for internal usage only and not officially supported. " + "You should not enable it unless somebody at Datadog instructed you to do so." + ) + endpoint = "https://intake.profile.{}".format(os.environ.get("DD_SITE", "datadoghq.com")) + else: + tracer_agent_url = tracer.agent_trace_url + endpoint = tracer_agent_url if tracer_agent_url else agent.get_trace_url() + return endpoint + + +def _get_endpoint_path(agentless=False) -> str: + if agentless: + endpoint_path = "/api/v2/profile" + else: + # path is relative because it is appended to the agent base path + endpoint_path = "profiling/v1/input" + return endpoint_path diff --git a/releasenotes/notes/profiling-dynamic-agent-url-1f285029201620b8.yaml b/releasenotes/notes/profiling-dynamic-agent-url-1f285029201620b8.yaml new file mode 100644 index 00000000000..e585f69fa14 --- /dev/null +++ b/releasenotes/notes/profiling-dynamic-agent-url-1f285029201620b8.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + profiling: profiler uses agent url configured via ``tracer.configure()`` diff --git a/tests/profiling/exporter/test_http.py b/tests/profiling/exporter/test_http.py index d38c02dafd1..42e3aeb6fe6 100644 --- a/tests/profiling/exporter/test_http.py +++ b/tests/profiling/exporter/test_http.py @@ -11,15 +11,11 @@ import pytest import ddtrace -from ddtrace.internal import compat from ddtrace.internal.processor.endpoint_call_counter import EndpointCallCounterProcessor from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.profiling import exporter from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config -from . import test_pprof - # Skip this test on Windows: # they add little value and the HTTP server shutdown seems unreliable and crashes @@ -183,10 +179,17 @@ def _get_span_processor(): return endpoint_call_counter_span_processor +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_ENDPOINT)) def test_wrong_api_key(endpoint_test_server): + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _get_span_processor + # This is mostly testing our test server, not the exporter exp = http.PprofHTTPExporter( - endpoint=_ENDPOINT, api_key="this is not the right API key", max_retry_delay=2, endpoint_call_counter_span_processor=_get_span_processor(), @@ -196,16 +199,29 @@ def test_wrong_api_key(endpoint_test_server): assert str(t.value) == "Server returned 400, check your API key" +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_ENDPOINT)) def test_export(endpoint_test_server): - exp = http.PprofHTTPExporter( - endpoint=_ENDPOINT, api_key=_API_KEY, endpoint_call_counter_span_processor=_get_span_processor() - ) + from ddtrace.internal import compat + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _API_KEY + from tests.profiling.exporter.test_http import _get_span_processor + + exp = http.PprofHTTPExporter(api_key=_API_KEY, endpoint_call_counter_span_processor=_get_span_processor()) exp.export(test_pprof.TEST_EVENTS, 0, compat.time_ns()) +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL="http://localhost:2")) def test_export_server_down(): + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _API_KEY + from tests.profiling.exporter.test_http import _get_span_processor + exp = http.PprofHTTPExporter( - endpoint="http://localhost:2", api_key=_API_KEY, max_retry_delay=2, endpoint_call_counter_span_processor=_get_span_processor(), @@ -214,9 +230,17 @@ def test_export_server_down(): exp.export(test_pprof.TEST_EVENTS, 0, 1) +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_TIMEOUT_ENDPOINT)) def test_export_timeout(endpoint_test_timeout_server): + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _API_KEY + from tests.profiling.exporter.test_http import _get_span_processor + exp = http.PprofHTTPExporter( - endpoint=_TIMEOUT_ENDPOINT, api_key=_API_KEY, timeout=1, max_retry_delay=2, @@ -226,9 +250,17 @@ def test_export_timeout(endpoint_test_timeout_server): exp.export(test_pprof.TEST_EVENTS, 0, 1) +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_RESET_ENDPOINT)) def test_export_reset(endpoint_test_reset_server): + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _API_KEY + from tests.profiling.exporter.test_http import _get_span_processor + exp = http.PprofHTTPExporter( - endpoint=_RESET_ENDPOINT, api_key=_API_KEY, timeout=1, max_retry_delay=2, @@ -238,8 +270,16 @@ def test_export_reset(endpoint_test_reset_server): exp.export(test_pprof.TEST_EVENTS, 0, 1) +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_UNKNOWN_ENDPOINT)) def test_export_404_agent(endpoint_test_unknown_server): - exp = http.PprofHTTPExporter(endpoint=_UNKNOWN_ENDPOINT, endpoint_call_counter_span_processor=_get_span_processor()) + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _get_span_processor + + exp = http.PprofHTTPExporter(endpoint_call_counter_span_processor=_get_span_processor()) with pytest.raises(exporter.ExportError) as t: exp.export(test_pprof.TEST_EVENTS, 0, 1) assert str(t.value) == ( @@ -247,41 +287,23 @@ def test_export_404_agent(endpoint_test_unknown_server): ) +@pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_UNKNOWN_ENDPOINT)) def test_export_404_agentless(endpoint_test_unknown_server): - exp = http.PprofHTTPExporter( - endpoint=_UNKNOWN_ENDPOINT, api_key="123", timeout=1, endpoint_call_counter_span_processor=_get_span_processor() - ) + import pytest + + from ddtrace.profiling import exporter + from ddtrace.profiling.exporter import http + from tests.profiling.exporter import test_pprof + from tests.profiling.exporter.test_http import _get_span_processor + + exp = http.PprofHTTPExporter(api_key="123", timeout=1, endpoint_call_counter_span_processor=_get_span_processor()) with pytest.raises(exporter.ExportError) as t: exp.export(test_pprof.TEST_EVENTS, 0, 1) assert str(t.value) == "HTTP Error 404" -def test_export_tracer_base_path(endpoint_test_server): - # Base path is prepended to the endpoint path because - # it does not start with a slash. - exp = http.PprofHTTPExporter( - endpoint=_ENDPOINT + "/profiling/", - api_key=_API_KEY, - endpoint_path="v1/input", - endpoint_call_counter_span_processor=_get_span_processor(), - ) - exp.export(test_pprof.TEST_EVENTS, 0, compat.time_ns()) - - -def test_export_tracer_base_path_agent_less(endpoint_test_server): - # Base path is ignored by the profiling HTTP exporter - # because the endpoint path starts with a slash. - exp = http.PprofHTTPExporter( - endpoint=_ENDPOINT + "/profiling/", - api_key=_API_KEY, - endpoint_path="/profiling/v1/input", - endpoint_call_counter_span_processor=_get_span_processor(), - ) - exp.export(test_pprof.TEST_EVENTS, 0, compat.time_ns()) - - def test_get_tags(): - tags = parse_tags_str(http.PprofHTTPExporter(env="foobar", endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(env="foobar", tags=config.tags)._get_tags("foobar")) assert tags["service"] == "foobar" assert len(tags["host"]) assert len(tags["runtime-id"]) @@ -298,7 +320,7 @@ def test_get_malformed_key_only(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) assert tags["service"] == "foobar" assert len(tags["host"]) assert len(tags["runtime-id"]) @@ -314,7 +336,7 @@ def test_get_malformed_no_val(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) assert tags["service"] == "foobar" assert len(tags["host"]) assert len(tags["runtime-id"]) @@ -330,7 +352,7 @@ def test_get_malformed_comma_only(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) assert tags["service"] == "foobar" assert len(tags["host"]) assert len(tags["runtime-id"]) @@ -346,7 +368,7 @@ def test_get_tags_trailing_comma(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) assert tags["service"] == "foobar" assert len(tags["host"]) @@ -368,9 +390,7 @@ def test_get_tags_override(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str( - http.PprofHTTPExporter(endpoint="", version="123", env="prod", tags=config.tags)._get_tags("foobar") - ) + tags = parse_tags_str(http.PprofHTTPExporter(version="123", env="prod", tags=config.tags)._get_tags("foobar")) assert tags["service"] == "🤣" assert len(tags["host"]) assert len(tags["runtime-id"]) @@ -399,7 +419,7 @@ def test_get_tags_precedence(): from ddtrace.profiling.exporter import http from ddtrace.settings.profiling import config - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) assert tags["mytag"] == "val2" assert tags["ddtag"] == "hi" assert tags["ddptag"] == "lo" @@ -417,7 +437,7 @@ def test_gitmetadata_ddtags(): from ddtrace.settings.profiling import config gitmetadata._GITMETADATA_TAGS = None - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) # must be from env variables assert tags["git.commit.sha"] == "12345" @@ -440,7 +460,7 @@ def test_gitmetadata_env(): gitmetadata._GITMETADATA_TAGS = None - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) # must be from env variables assert tags["git.commit.sha"] == "123456" @@ -466,7 +486,7 @@ def test_gitmetadata_disabled(monkeypatch): gitmetadata._GITMETADATA_TAGS = None - tags = parse_tags_str(http.PprofHTTPExporter(endpoint="", tags=config.tags)._get_tags("foobar")) + tags = parse_tags_str(http.PprofHTTPExporter(tags=config.tags)._get_tags("foobar")) # must not present assert "git.commit.sha" not in tags diff --git a/tests/profiling/test_profiler.py b/tests/profiling/test_profiler.py index 8bb092bfef6..7f98bbf6aa8 100644 --- a/tests/profiling/test_profiler.py +++ b/tests/profiling/test_profiler.py @@ -124,7 +124,6 @@ def test_env_default(): prof = profiler.Profiler() assert prof.env == "staging" assert prof.version == "123" - assert prof.url is None for exp in prof._profiler._scheduler.exporters: if isinstance(exp, http.PprofHTTPExporter): assert exp.env == "staging" @@ -138,7 +137,6 @@ def test_env_api(): prof = profiler.Profiler(env="staging", version="123") assert prof.env == "staging" assert prof.version == "123" - assert prof.url is None for exp in prof._profiler._scheduler.exporters: if isinstance(exp, http.PprofHTTPExporter): assert exp.env == "staging" @@ -152,7 +150,6 @@ def test_tags_api(): prof = profiler.Profiler(env="staging", version="123", tags={"foo": "bar"}) assert prof.env == "staging" assert prof.version == "123" - assert prof.url is None assert prof.tags["foo"] == "bar" for exp in prof._profiler._scheduler.exporters: if isinstance(exp, http.PprofHTTPExporter): @@ -224,11 +221,6 @@ def test_env_no_agentless(): _check_url(prof, "http://localhost:8126", "foobar") -def test_url(): - prof = profiler.Profiler(url="https://foobar:123") - _check_url(prof, "https://foobar:123", os.environ.get("DD_API_KEY")) - - def _check_url(prof, url, api_key, endpoint_path="profiling/v1/input"): for exp in prof._profiler._scheduler.exporters: if isinstance(exp, http.PprofHTTPExporter): @@ -240,22 +232,6 @@ def _check_url(prof, url, api_key, endpoint_path="profiling/v1/input"): pytest.fail("Unable to find HTTP exporter") -def test_default_tracer_and_url(): - try: - ddtrace.tracer.configure(hostname="foobar") - prof = profiler.Profiler(url="https://foobaz:123") - _check_url(prof, "https://foobaz:123", os.environ.get("DD_API_KEY")) - finally: - ddtrace.tracer.configure(hostname="localhost") - - -def test_tracer_and_url(): - t = ddtrace.Tracer() - t.configure(hostname="foobar") - prof = profiler.Profiler(tracer=t, url="https://foobaz:123") - _check_url(prof, "https://foobaz:123", os.environ.get("DD_API_KEY")) - - def test_tracer_url(): t = ddtrace.Tracer() t.configure(hostname="foobar") @@ -284,6 +260,13 @@ def test_tracer_url_uds(): _check_url(prof, "unix:///foobar", os.environ.get("DD_API_KEY")) +def test_tracer_url_configure_after(): + t = ddtrace.Tracer() + prof = profiler.Profiler(tracer=t) + t.configure(hostname="foobar") + _check_url(prof, "http://foobar:8126", os.environ.get("DD_API_KEY")) + + def test_env_no_api_key(): prof = profiler.Profiler() _check_url(prof, "http://localhost:8126", os.environ.get("DD_API_KEY")) diff --git a/tests/profiling_v2/exporter/test_ddup.py b/tests/profiling_v2/exporter/test_ddup.py index 54f96fe6cb7..9fca404c65f 100644 --- a/tests/profiling_v2/exporter/test_ddup.py +++ b/tests/profiling_v2/exporter/test_ddup.py @@ -26,7 +26,6 @@ def test_ddup_start(): service="my_service", version="my_version", tags={}, - url="http://localhost:8126", ) ddup.start() except Exception as e: From 1315715730edf3001820a3ed9ccc180b5820702b Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Mon, 25 Nov 2024 10:39:01 +0100 Subject: [PATCH 218/372] chore(ci) : remove apm stress test from ci (#11484) This workflow was failing for the past 6 months. We'll fix it and run it directly from the main repo with a proper follow-up. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) https://datadoghq.atlassian.net/browse/APMSP-1578 --- .../workflows/apm-transport-stress-test.yml | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/apm-transport-stress-test.yml diff --git a/.github/workflows/apm-transport-stress-test.yml b/.github/workflows/apm-transport-stress-test.yml deleted file mode 100644 index c581629bbe7..00000000000 --- a/.github/workflows/apm-transport-stress-test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: 'Run Datadog APM Transport Stress Tests' -on: - workflow_dispatch: - schedule: - # Every hour - - cron: '0 * * * *' - -jobs: - run_stress_tests: - runs-on: ubuntu-latest - env: - AGENT_DOCKERFILE: realagent - DD_API_KEY: ${{ secrets.DD_SHARED_TESTS_API_KEY }} - TRACER: python - steps: - - uses: actions/checkout@v4 - with: - repository: DataDog/apm-transport-stress-tests - - name: Build - run: ./build.sh "${TRACER}" "${AGENT_DOCKERFILE}" - - name: Test TCPIP - run: ./run.sh tcpip - env: - RUN_ID: ${{ github.run_id }} - - name: Test UDS - run: ./run.sh uds - env: - RUN_ID: ${{ github.run_id }} From 6205242103a2f232001152a749b8c296540cdc05 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 25 Nov 2024 13:27:42 +0100 Subject: [PATCH 219/372] chore(iast): check the return value of astpatch_module (#11520) ## Description Some testing code were not checking the return value of `astpatch_module` as it should, making some package tests fail after a previous PR that fixed the return value of `astpatch_module` when no patching was done. Also disable `google.*` from package tests since we have disabled patching for those. Signed-off-by: Juanjo Alvarez ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_iast/__init__.py | 4 ++ .../appsec/iast_packages/inside_env_runner.py | 3 ++ tests/appsec/iast_packages/test_packages.py | 42 +++++++++---------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index c7196436fa0..b98912b0410 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -63,6 +63,10 @@ def ddtrace_iast_flask_patch(): log.debug("Unexpected exception while AST patching", exc_info=True) return + if not patched_ast: + log.debug("Main flask module not patched, probably it was not needed") + return + compiled_code = compile(patched_ast, module_path, "exec") exec(compiled_code, module.__dict__) # nosec B102 sys.modules[module_name] = compiled_code diff --git a/tests/appsec/iast_packages/inside_env_runner.py b/tests/appsec/iast_packages/inside_env_runner.py index bfd8f6d2eb8..77f7c52c0e5 100644 --- a/tests/appsec/iast_packages/inside_env_runner.py +++ b/tests/appsec/iast_packages/inside_env_runner.py @@ -16,6 +16,9 @@ def _iast_patched_module_and_patched_source(module_name): module = importlib.import_module(module_name) module_path, patched_module = astpatch_module(module) + if not patched_module: + assert False, "Module %s was not patched" % module_name + compiled_code = compile(patched_module, module_path, "exec") exec(compiled_code, module.__dict__) return module, patched_module diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index cde0bf5a89c..048fa2abe05 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -266,27 +266,27 @@ def uninstall(self, python_cmd): ), PackageForTesting("flask", "2.3.3", "", "", "", test_e2e=False, import_module_to_validate="flask.app"), PackageForTesting("fsspec", "2024.5.0", "", "/", ""), - PackageForTesting( - "google-auth", - "2.35.0", - "", - "", - "", - import_name="google.auth.crypt.rsa", - import_module_to_validate="google.auth.crypt.rsa", - expect_no_change=True, - ), - PackageForTesting( - "google-api-core", - "2.22.0", - "", - "", - "", - import_name="google", - import_module_to_validate="google.auth.iam", - extras=[("google-cloud-storage", "2.18.2")], - test_e2e=True, - ), + # PackageForTesting( + # "google-auth", + # "2.35.0", + # "", + # "", + # "", + # import_name="google.auth.crypt.rsa", + # import_module_to_validate="google.auth.crypt.rsa", + # expect_no_change=True, + # ), + # PackageForTesting( + # "google-api-core", + # "2.22.0", + # "", + # "", + # "", + # import_name="google", + # import_module_to_validate="google.auth.iam", + # extras=[("google-cloud-storage", "2.18.2")], + # test_e2e=True, + # ), PackageForTesting( "google-api-python-client", "2.111.0", From 34d69beb65b0b3cd4c7d8a63f8f3a46b3e2e83f6 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 25 Nov 2024 14:00:48 +0000 Subject: [PATCH 220/372] ci: enable exception replay in Django profile job (#11014) We enable Exception Replay in the Django profile workflow and create a scenario that leads to an exception to profile the relevant code paths. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/django-overhead-profile.yml | 1 + scripts/profiles/django-simple/k6-exc.js | 10 ++ scripts/profiles/django-simple/k6-load.js | 3 +- scripts/profiles/django-simple/run.sh | 100 +++++++++++------- scripts/profiles/django-simple/setup.sh | 10 +- 5 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 scripts/profiles/django-simple/k6-exc.js diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index 55f023f0c8f..6d026338ca9 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -26,6 +26,7 @@ jobs: DD_PROFILING_ENABLED: "1" DD_PROFILING_STACK_V2_ENABLED: ${{ matrix.stack_v2 }} DD_PROFILING_OUTPUT_PPROF: ${{ github.workspace }}/prefix/artifacts/ddtrace_profile + DD_EXCEPTION_REPLAY_ENABLED: "1" defaults: run: working-directory: ddtrace diff --git a/scripts/profiles/django-simple/k6-exc.js b/scripts/profiles/django-simple/k6-exc.js new file mode 100644 index 00000000000..106652037bc --- /dev/null +++ b/scripts/profiles/django-simple/k6-exc.js @@ -0,0 +1,10 @@ +import http from 'k6/http'; + +export const options = { + duration: '60s', + vus: 100, +}; + +export default function () { + const res = http.get('http://127.0.0.1:8080/polls/123/results/'); +} diff --git a/scripts/profiles/django-simple/k6-load.js b/scripts/profiles/django-simple/k6-load.js index 5439e80ea16..9e09f108e01 100644 --- a/scripts/profiles/django-simple/k6-load.js +++ b/scripts/profiles/django-simple/k6-load.js @@ -1,5 +1,4 @@ import http from 'k6/http'; -import { sleep } from 'k6'; export const options = { duration: '60s', @@ -7,5 +6,5 @@ export const options = { }; export default function () { - const res = http.get('http://127.0.0.1:8000/accounts/signup/'); + const res = http.get('http://127.0.0.1:8080/polls/123/vote/'); } diff --git a/scripts/profiles/django-simple/run.sh b/scripts/profiles/django-simple/run.sh index 109f6b30c51..fcfbbf9bfe6 100755 --- a/scripts/profiles/django-simple/run.sh +++ b/scripts/profiles/django-simple/run.sh @@ -9,59 +9,83 @@ AUSTIN_EXPOSURE=5 # sec test -f ${PREFIX}/gunicorn.pid && (kill -9 `cat ${PREFIX}/gunicorn.pid` ; sleep 3) || rm -f ${PREFIX}/gunicorn.pid pkill k6 || true -test -d ${PREFIX}/artifacts && rm -rf ${PREFIX}/artifacts || mkdir -p ${PREFIX}/artifacts +pkill -9 -f uwsgi || true +test -d ${PREFIX}/artifacts && rm -rf ${PREFIX}/artifacts +mkdir -p ${PREFIX}/artifacts sudo echo "sudo OK" +sudo rm -f ${PREFIX}/uwsgi.pid + function profile_with_load { name=${1} + scenario=${2} + + echo "- profiling for ${name}" sleep 3 - ${PREFIX}/k6*/k6 run --quiet scripts/profiles/django-simple/k6-load.js & + echo "Starting load" + ${PREFIX}/k6*/k6 run --quiet scripts/profiles/django-simple/k6-${scenario}.js & sleep 2 - sudo `which austin` -bsCi ${AUSTIN_INTERVAL} -o ${PREFIX}/artifacts/${name}.mojo -p `cat ${PREFIX}/gunicorn.pid` -x ${AUSTIN_EXPOSURE} - LC_ALL=C sed -i 's|/home/runner/work/dd-trace-py/dd-trace-py/ddtrace/||g' ${PREFIX}/artifacts/${name}.mojo + echo "Attaching Austin to $(cat ${PREFIX}/uwsgi.pid)" + sudo `which austin` -bsCi ${AUSTIN_INTERVAL} -o ${PREFIX}/artifacts/${scenario}_${name}.mojo -p `cat ${PREFIX}/uwsgi.pid` -x ${AUSTIN_EXPOSURE} + LC_ALL=C sed -i 's|/home/runner/work/dd-trace-py/dd-trace-py/ddtrace/||g' ${PREFIX}/artifacts/${scenario}_${name}.mojo + echo "Stopping load" pkill k6 } source ${PREFIX}/bin/activate -export DJANGO_SETTINGS_MODULE="config.settings.production" -export DJANGO_ALLOWED_HOSTS="127.0.0.1" -export DJANGO_SECRET_KEY="SECRET_KEY" export DATABASE_URL="sqlite:///django.db" +export DJANGO_ALLOWED_HOSTS="127.0.0.1" +export DEVELOPMENT_MODE=True # Tag traces with HTTP headers to benchmark the related code export DD_TRACE_HEADER_TAGS="User-Agent:http.user_agent,Referer:http.referer,Content-Type:http.content_type,Etag:http.etag" -# Baseline -pushd ${PREFIX}/trace-examples/python/django/django-simple - gunicorn config.wsgi --pid ${PREFIX}/gunicorn.pid > /dev/null & - echo "Done" -popd -profile_with_load "baseline" -kill $(cat ${PREFIX}/gunicorn.pid) - -pushd ${PREFIX}/trace-examples/python/django/django-simple - ddtrace-run gunicorn config.wsgi --pid ${PREFIX}/gunicorn.pid > /dev/null & -popd -profile_with_load "head" -kill $(cat ${PREFIX}/gunicorn.pid) - -sudo chown -R $(id -u):$(id -g) ${PREFIX}/artifacts/* - -echo -n "Converting MOJO to Austin ... " -mojo2austin ${PREFIX}/artifacts/head.mojo ${PREFIX}/artifacts/head.austin.tmp -mojo2austin ${PREFIX}/artifacts/baseline.mojo ${PREFIX}/artifacts/baseline.austin.tmp -echo "[done]" - -echo -n "Diffing ... " -python scripts/diff.py \ - ${PREFIX}/artifacts/head.austin.tmp \ - ${PREFIX}/artifacts/baseline.austin.tmp \ - ${PREFIX}/artifacts/baseline_head.diff -echo "[done]" - -rm ${PREFIX}/artifacts/*.austin.tmp - -head -n 25 ${PREFIX}/artifacts/baseline_head.diff.top + +function run_scenario { + scenario=${1} + + echo "Running scenario ${scenario}" + + # Baseline + pushd ${PREFIX}/trace-examples/python/django/sample-django + uwsgi --http :8080 --enable-threads --module mysite.wsgi --pidfile ${PREFIX}/uwsgi.pid 2> /dev/null & + echo "Done" + popd + profile_with_load "baseline" ${scenario} + echo "Stopping uwsgi" + uwsgi --stop ${PREFIX}/uwsgi.pid + pkill -9 -f uwsgi || true + + pushd ${PREFIX}/trace-examples/python/django/sample-django + uwsgi --http :8080 --enable-threads --module mysite.wsgi --pidfile ${PREFIX}/uwsgi.pid --import=ddtrace.bootstrap.sitecustomize 2> /dev/null & + popd + profile_with_load "head" ${scenario} + echo "Stopping uwsgi" + uwsgi --stop ${PREFIX}/uwsgi.pid + pkill -9 -f uwsgi || true + + sudo chown -R $(id -u):$(id -g) ${PREFIX}/artifacts/* + + echo -n "Converting MOJO to Austin ... " + mojo2austin ${PREFIX}/artifacts/${scenario}_head.mojo ${PREFIX}/artifacts/${scenario}_head.austin.tmp + mojo2austin ${PREFIX}/artifacts/${scenario}_baseline.mojo ${PREFIX}/artifacts/${scenario}_baseline.austin.tmp + echo "[done]" + + echo -n "Diffing ... " + python scripts/diff.py \ + ${PREFIX}/artifacts/${scenario}_head.austin.tmp \ + ${PREFIX}/artifacts/${scenario}_baseline.austin.tmp \ + ${PREFIX}/artifacts/${scenario}_baseline_head.diff + echo "[done]" + + rm ${PREFIX}/artifacts/*.austin.tmp + + head -n 25 ${PREFIX}/artifacts/${scenario}_baseline_head.diff.top +} + + +run_scenario "load" +run_scenario "exc" diff --git a/scripts/profiles/django-simple/setup.sh b/scripts/profiles/django-simple/setup.sh index cc6c20e9a03..2f14db6ee4d 100755 --- a/scripts/profiles/django-simple/setup.sh +++ b/scripts/profiles/django-simple/setup.sh @@ -6,9 +6,7 @@ PREFIX=${1} AUSTIN_VERSION="3.6" K6_VERSION="0.26.2" -export DJANGO_SETTINGS_MODULE="config.settings.production" export DJANGO_ALLOWED_HOSTS="127.0.0.1" -export DJANGO_SECRET_KEY="SECRET_KEY" export DATABASE_URL="sqlite:///django.db" # Clean up existing installation @@ -26,12 +24,12 @@ source ${PREFIX}/bin/activate pip install pip --upgrade # Install the application -git clone https://github.com/DataDog/trace-examples.git ${PREFIX}/trace-examples +test -d ${PREFIX}/trace-examples || git clone -b sample-django --single-branch https://github.com/DataDog/trace-examples.git ${PREFIX}/trace-examples pushd ${PREFIX}/trace-examples/ - git checkout origin/django-simple - pushd python/django/django-simple - pip install -r requirements/production.txt + pushd python/django/sample-django + pip install -r requirements.txt python manage.py migrate + python manage.py collectstatic popd popd From 00440f5b914f9b4666c91cd2eefa4cbc21585ffa Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Mon, 25 Nov 2024 15:50:34 +0100 Subject: [PATCH 221/372] ci: enable iast standalone system tests (#11507) --- .github/workflows/system-tests.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index e692589a702..e7edf051ecc 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -133,11 +133,6 @@ jobs: docker load < images_artifacts/${{ matrix.weblog-variant}}_weblog_${{ github.sha }}.tar.gz docker load < images_artifacts/agent_${{ github.sha }}.tar.gz - # TODO: Enable once https://github.com/DataDog/system-tests/pull/3506 is merged - # - name: Run IAST_STANDALONE - # if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' - # run: ./run.sh IAST_STANDALONE - - name: Run DEFAULT if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'other' run: ./run.sh DEFAULT @@ -202,6 +197,10 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' run: ./run.sh APPSEC_STANDALONE + - name: Run IAST_STANDALONE + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' + run: ./run.sh IAST_STANDALONE + - name: Run APPSEC_RUNTIME_ACTIVATION if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' run: ./run.sh APPSEC_RUNTIME_ACTIVATION From 451d88d5346c75c4243d7cf982eed4765a8cd8cd Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:23:36 -0500 Subject: [PATCH 222/372] fix(llmobs): sync and async generators can be decorated (#11413) MLOB-1892 ## What does this PR do Fixes support for decorating sync and async generator functions. Some notes: - While we will still capture function name and inputs (inputs only for non `llm` and `embedding` decorators), we will not automatically capture outputs from the generator - This change is intended to allow user annotation to still be captured, as previously, the generator would be captured as the "output", and user annotation would never go through. - Async generators are not identifiable by `iscoroutine` and `isgeneratorfunction`, so we rely on `isasyncgenfunction` instead, verified in tests. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/decorators.py | 158 ++++++-- ...enerators-decorating-6b1c4bcf2462d218.yaml | 4 + tests/llmobs/test_llmobs_decorators.py | 350 ++++++++++++++++++ 3 files changed, 486 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/llmobs-generators-decorating-6b1c4bcf2462d218.yaml diff --git a/ddtrace/llmobs/decorators.py b/ddtrace/llmobs/decorators.py index 86d8a1c2593..93f329f2889 100644 --- a/ddtrace/llmobs/decorators.py +++ b/ddtrace/llmobs/decorators.py @@ -1,9 +1,12 @@ from functools import wraps +from inspect import isasyncgenfunction from inspect import signature +import sys from typing import Callable from typing import Optional from ddtrace.internal.compat import iscoroutinefunction +from ddtrace.internal.compat import isgeneratorfunction from ddtrace.internal.logger import get_logger from ddtrace.llmobs import LLMObs from ddtrace.llmobs._constants import OUTPUT_VALUE @@ -13,6 +16,44 @@ log = get_logger(__name__) +def _get_llmobs_span_options(name, model_name, func): + traced_model_name = model_name + if traced_model_name is None: + traced_model_name = "custom" + + span_name = name + if span_name is None: + span_name = func.__name__ + + return traced_model_name, span_name + + +async def yield_from_async_gen(func, span, args, kwargs): + try: + gen = func(*args, **kwargs) + next_val = await gen.asend(None) + while True: + try: + i = yield next_val + next_val = await gen.asend(i) + except GeneratorExit: + await gen.aclose() + break + except StopAsyncIteration as e: + await gen.athrow(e) + break + except Exception as e: + await gen.athrow(e) + raise + except (StopAsyncIteration, GeneratorExit): + raise + except Exception: + span.set_exc_info(*sys.exc_info()) + raise + finally: + span.finish() + + def _model_decorator(operation_kind): def decorator( original_func: Optional[Callable] = None, @@ -23,20 +64,31 @@ def decorator( ml_app: Optional[str] = None, ): def inner(func): - if iscoroutinefunction(func): + if iscoroutinefunction(func) or isasyncgenfunction(func): + + @wraps(func) + def generator_wrapper(*args, **kwargs): + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) + return func(*args, **kwargs) + traced_model_name, span_name = _get_llmobs_span_options(name, model_name, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.llm) + span = traced_operation( + model_name=traced_model_name, + model_provider=model_provider, + name=span_name, + session_id=session_id, + ml_app=ml_app, + ) + return yield_from_async_gen(func, span, args, kwargs) @wraps(func) async def wrapper(*args, **kwargs): if not LLMObs.enabled: log.warning(SPAN_START_WHILE_DISABLED_WARNING) return await func(*args, **kwargs) - traced_model_name = model_name - if traced_model_name is None: - traced_model_name = "custom" - span_name = name - if span_name is None: - span_name = func.__name__ - traced_operation = getattr(LLMObs, operation_kind, "llm") + traced_model_name, span_name = _get_llmobs_span_options(name, model_name, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.llm) with traced_operation( model_name=traced_model_name, model_provider=model_provider, @@ -48,18 +100,38 @@ async def wrapper(*args, **kwargs): else: + @wraps(func) + def generator_wrapper(*args, **kwargs): + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) + yield from func(*args, **kwargs) + else: + traced_model_name, span_name = _get_llmobs_span_options(name, model_name, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.llm) + span = traced_operation( + model_name=traced_model_name, + model_provider=model_provider, + name=span_name, + session_id=session_id, + ml_app=ml_app, + ) + try: + yield from func(*args, **kwargs) + except (StopIteration, GeneratorExit): + raise + except Exception: + span.set_exc_info(*sys.exc_info()) + raise + finally: + span.finish() + @wraps(func) def wrapper(*args, **kwargs): if not LLMObs.enabled: log.warning(SPAN_START_WHILE_DISABLED_WARNING) return func(*args, **kwargs) - traced_model_name = model_name - if traced_model_name is None: - traced_model_name = "custom" - span_name = name - if span_name is None: - span_name = func.__name__ - traced_operation = getattr(LLMObs, operation_kind, "llm") + traced_model_name, span_name = _get_llmobs_span_options(name, model_name, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.llm) with traced_operation( model_name=traced_model_name, model_provider=model_provider, @@ -69,7 +141,7 @@ def wrapper(*args, **kwargs): ): return func(*args, **kwargs) - return wrapper + return generator_wrapper if (isgeneratorfunction(func) or isasyncgenfunction(func)) else wrapper if original_func and callable(original_func): return inner(original_func) @@ -87,17 +159,29 @@ def decorator( _automatic_io_annotation: bool = True, ): def inner(func): - if iscoroutinefunction(func): + if iscoroutinefunction(func) or isasyncgenfunction(func): + + @wraps(func) + def generator_wrapper(*args, **kwargs): + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) + return func(*args, **kwargs) + _, span_name = _get_llmobs_span_options(name, None, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.workflow) + span = traced_operation(name=span_name, session_id=session_id, ml_app=ml_app) + func_signature = signature(func) + bound_args = func_signature.bind_partial(*args, **kwargs) + if _automatic_io_annotation and bound_args.arguments: + LLMObs.annotate(span=span, input_data=bound_args.arguments) + return yield_from_async_gen(func, span, args, kwargs) @wraps(func) async def wrapper(*args, **kwargs): if not LLMObs.enabled: log.warning(SPAN_START_WHILE_DISABLED_WARNING) return await func(*args, **kwargs) - span_name = name - if span_name is None: - span_name = func.__name__ - traced_operation = getattr(LLMObs, operation_kind, "workflow") + _, span_name = _get_llmobs_span_options(name, None, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.workflow) with traced_operation(name=span_name, session_id=session_id, ml_app=ml_app) as span: func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) @@ -115,15 +199,37 @@ async def wrapper(*args, **kwargs): else: + @wraps(func) + def generator_wrapper(*args, **kwargs): + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) + yield from func(*args, **kwargs) + else: + _, span_name = _get_llmobs_span_options(name, None, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.workflow) + span = traced_operation(name=span_name, session_id=session_id, ml_app=ml_app) + func_signature = signature(func) + bound_args = func_signature.bind_partial(*args, **kwargs) + if _automatic_io_annotation and bound_args.arguments: + LLMObs.annotate(span=span, input_data=bound_args.arguments) + try: + yield from func(*args, **kwargs) + except (StopIteration, GeneratorExit): + raise + except Exception: + span.set_exc_info(*sys.exc_info()) + raise + finally: + if span: + span.finish() + @wraps(func) def wrapper(*args, **kwargs): if not LLMObs.enabled: log.warning(SPAN_START_WHILE_DISABLED_WARNING) return func(*args, **kwargs) - span_name = name - if span_name is None: - span_name = func.__name__ - traced_operation = getattr(LLMObs, operation_kind, "workflow") + _, span_name = _get_llmobs_span_options(name, None, func) + traced_operation = getattr(LLMObs, operation_kind, LLMObs.workflow) with traced_operation(name=span_name, session_id=session_id, ml_app=ml_app) as span: func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) @@ -139,7 +245,7 @@ def wrapper(*args, **kwargs): LLMObs.annotate(span=span, output_data=resp) return resp - return wrapper + return generator_wrapper if (isgeneratorfunction(func) or isasyncgenfunction(func)) else wrapper if original_func and callable(original_func): return inner(original_func) diff --git a/releasenotes/notes/llmobs-generators-decorating-6b1c4bcf2462d218.yaml b/releasenotes/notes/llmobs-generators-decorating-6b1c4bcf2462d218.yaml new file mode 100644 index 00000000000..fe46be4bf62 --- /dev/null +++ b/releasenotes/notes/llmobs-generators-decorating-6b1c4bcf2462d218.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + LLM Observability: Fixes an issue where decorators were not tracing generator functions properly. diff --git a/tests/llmobs/test_llmobs_decorators.py b/tests/llmobs/test_llmobs_decorators.py index 82fede35ed2..347fb55f652 100644 --- a/tests/llmobs/test_llmobs_decorators.py +++ b/tests/llmobs/test_llmobs_decorators.py @@ -536,3 +536,353 @@ def f(prompt, arg_2, kwarg_1=None, kwarg_2=None): output_value="my custom output", ) ) + + +def test_generator_sync(LLMObs, mock_llmobs_span_writer): + """ + Test that decorators work with generator functions. + The span should finish after the generator is exhausted. + """ + for decorator_name, decorator in ( + ("task", task), + ("workflow", workflow), + ("tool", tool), + ("agent", agent), + ("retrieval", retrieval), + ("llm", llm), + ("embedding", embedding), + ): + + @decorator() + def f(): + for i in range(3): + yield i + + LLMObs.annotate( + input_data="hello", + output_data="world", + ) + + i = 0 + for e in f(): + assert e == i + i += 1 + + span = LLMObs._instance.tracer.pop()[0] + if decorator_name == "llm": + expected_span_event = _expected_llmobs_llm_span_event( + span, + decorator_name, + input_messages=[{"content": "hello"}], + output_messages=[{"content": "world"}], + model_name="custom", + model_provider="custom", + ) + elif decorator_name == "embedding": + expected_span_event = _expected_llmobs_llm_span_event( + span, + decorator_name, + input_documents=[{"text": "hello"}], + output_value="world", + model_name="custom", + model_provider="custom", + ) + elif decorator_name == "retrieval": + expected_span_event = _expected_llmobs_non_llm_span_event( + span, decorator_name, input_value="hello", output_documents=[{"text": "world"}] + ) + else: + expected_span_event = _expected_llmobs_non_llm_span_event( + span, decorator_name, input_value="hello", output_value="world" + ) + + mock_llmobs_span_writer.enqueue.assert_called_with(expected_span_event) + + +async def test_generator_async(LLMObs, mock_llmobs_span_writer): + """ + Test that decorators work with generator functions. + The span should finish after the generator is exhausted. + """ + for decorator_name, decorator in ( + ("task", task), + ("workflow", workflow), + ("tool", tool), + ("agent", agent), + ("retrieval", retrieval), + ("llm", llm), + ("embedding", embedding), + ): + + @decorator() + async def f(): + for i in range(3): + yield i + + LLMObs.annotate( + input_data="hello", + output_data="world", + ) + + i = 0 + async for e in f(): + assert e == i + i += 1 + + span = LLMObs._instance.tracer.pop()[0] + if decorator_name == "llm": + expected_span_event = _expected_llmobs_llm_span_event( + span, + decorator_name, + input_messages=[{"content": "hello"}], + output_messages=[{"content": "world"}], + model_name="custom", + model_provider="custom", + ) + elif decorator_name == "embedding": + expected_span_event = _expected_llmobs_llm_span_event( + span, + decorator_name, + input_documents=[{"text": "hello"}], + output_value="world", + model_name="custom", + model_provider="custom", + ) + elif decorator_name == "retrieval": + expected_span_event = _expected_llmobs_non_llm_span_event( + span, decorator_name, input_value="hello", output_documents=[{"text": "world"}] + ) + else: + expected_span_event = _expected_llmobs_non_llm_span_event( + span, decorator_name, input_value="hello", output_value="world" + ) + + mock_llmobs_span_writer.enqueue.assert_called_with(expected_span_event) + + +def test_generator_sync_with_llmobs_disabled(LLMObs, mock_logs): + LLMObs.disable() + + @workflow() + def f(): + for i in range(3): + yield i + + i = 0 + for e in f(): + assert e == i + i += 1 + + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) + + @llm() + def g(): + for i in range(3): + yield i + + i = 0 + for e in g(): + assert e == i + i += 1 + + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) + + +async def test_generator_async_with_llmobs_disabled(LLMObs, mock_logs): + LLMObs.disable() + + @workflow() + async def f(): + for i in range(3): + yield i + + i = 0 + async for e in f(): + assert e == i + i += 1 + + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) + + @llm() + async def g(): + for i in range(3): + yield i + + i = 0 + async for e in g(): + assert e == i + i += 1 + + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) + + +def test_generator_sync_finishes_span_on_error(LLMObs, mock_llmobs_span_writer): + """Tests that""" + + @workflow() + def f(): + for i in range(3): + if i == 1: + raise ValueError("test_error") + yield i + + with pytest.raises(ValueError): + for _ in f(): + pass + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + error=span.get_tag("error.type"), + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + ) + ) + + +async def test_generator_async_finishes_span_on_error(LLMObs, mock_llmobs_span_writer): + @workflow() + async def f(): + for i in range(3): + if i == 1: + raise ValueError("test_error") + yield i + + with pytest.raises(ValueError): + async for _ in f(): + pass + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + error=span.get_tag("error.type"), + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + ) + ) + + +def test_generator_sync_send(LLMObs, mock_llmobs_span_writer): + @workflow() + def f(): + while True: + i = yield + yield i**2 + + gen = f() + next(gen) + assert gen.send(2) == 4 + next(gen) + assert gen.send(3) == 9 + next(gen) + assert gen.send(4) == 16 + gen.close() + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + ) + ) + + +async def test_generator_async_send(LLMObs, mock_llmobs_span_writer): + @workflow() + async def f(): + while True: + value = yield + yield value**2 + + gen = f() + await gen.asend(None) # Prime the generator + + for i in range(5): + assert (await gen.asend(i)) == i**2 + await gen.asend(None) + + await gen.aclose() + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + ) + ) + + +def test_generator_sync_throw(LLMObs, mock_llmobs_span_writer): + @workflow() + def f(): + for i in range(3): + yield i + + with pytest.raises(ValueError): + gen = f() + next(gen) + gen.throw(ValueError("test_error")) + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + error=span.get_tag("error.type"), + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + ) + ) + + +async def test_generator_async_throw(LLMObs, mock_llmobs_span_writer): + @workflow() + async def f(): + for i in range(3): + yield i + + with pytest.raises(ValueError): + gen = f() + await gen.asend(None) + await gen.athrow(ValueError("test_error")) + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + error=span.get_tag("error.type"), + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + ) + ) + + +def test_generator_exit_exception_sync(LLMObs, mock_llmobs_span_writer): + @workflow() + def get_next_element(alist): + for element in alist: + try: + yield element + except BaseException: # except Exception + pass + + for element in get_next_element([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]): + if element == 5: + break + + span = LLMObs._instance.tracer.pop()[0] + mock_llmobs_span_writer.enqueue.assert_called_with( + _expected_llmobs_non_llm_span_event( + span, + "workflow", + input_value=json.dumps({"alist": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}), + error=span.get_tag("error.type"), + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + ) + ) From 81824b8ba43c88164d4e709097c7772a9846150f Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:40:35 +0100 Subject: [PATCH 223/372] fix(asm): add global states to ensure patching once (#11522) Ensure common patches for SCA and Exploit Prevention are loaded.. - only once - only if exploit prevention is active or sca is active Changes: - factorize load_common_modules logic in ddtrace.appsec - boolean state for patch_common_module and enable_iast_propagation to ensure they are only called once. - ensure it's loaded after one click activation - ensure it's properly loaded in unit tests if required - add some failsafe for iast in wrap_open for importerror - update an iast test to reflect that common_modules is loaded in the test by default. APPSEC-55997 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_monkey.py | 8 +++----- ddtrace/appsec/__init__.py | 16 +++++++++++++++ ddtrace/appsec/_common_module_patches.py | 20 ++++++++++++++++--- ddtrace/appsec/_iast/__init__.py | 12 +++++++++++ ddtrace/appsec/_remoteconfiguration.py | 5 +++++ ...prevention_patch_fix-1bdd7540e1d085d8.yaml | 4 ++++ .../integrations/test_flask_telemetry.py | 4 +++- tests/utils.py | 2 ++ 8 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 15778c2c95a..d203ca19e24 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -5,12 +5,13 @@ from wrapt.importer import when_imported +from ddtrace.appsec import load_common_appsec_modules + from .appsec._iast._utils import _is_iast_enabled from .internal import telemetry from .internal.logger import get_logger from .internal.utils import formats from .settings import _config as config -from .settings.asm import config as asm_config if TYPE_CHECKING: # pragma: no cover @@ -234,10 +235,7 @@ def patch_all(**patch_modules): patch_iast() enable_iast_propagation() - if asm_config._ep_enabled or asm_config._iast_enabled: - from ddtrace.appsec._common_module_patches import patch_common_modules - - patch_common_modules() + load_common_appsec_modules() def patch(raise_errors=True, patch_modules_prefix=DEFAULT_MODULES_PREFIX, **patch_modules): diff --git a/ddtrace/appsec/__init__.py b/ddtrace/appsec/__init__.py index 27463c7c8a1..bc89c0f2127 100644 --- a/ddtrace/appsec/__init__.py +++ b/ddtrace/appsec/__init__.py @@ -1,3 +1,7 @@ +from ddtrace.internal import core +from ddtrace.settings.asm import config as asm_config + + _APPSEC_TO_BE_LOADED = True _IAST_TO_BE_LOADED = True @@ -20,3 +24,15 @@ def load_iast(): if _IAST_TO_BE_LOADED: iast_listen() _IAST_TO_BE_LOADED = False + + +def load_common_appsec_modules(): + """Lazily load the common module patches.""" + if (asm_config._ep_enabled and asm_config._asm_enabled) or asm_config._iast_enabled: + from ddtrace.appsec._common_module_patches import patch_common_modules + + patch_common_modules() + + +# for tests that needs to load the appsec module later +core.on("test.config.override", load_common_appsec_modules) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 494e8e63fb7..a5ab2d1533d 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -27,8 +27,13 @@ log = get_logger(__name__) _DD_ORIGINAL_ATTRIBUTES: Dict[Any, Any] = {} +_is_patched = False + def patch_common_modules(): + global _is_patched + if _is_patched: + return try_wrap_function_wrapper("builtins", "open", wrapped_open_CFDDB7ABBA9081B6) try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF) try_wrap_function_wrapper("_io", "BytesIO.read", wrapped_read_F3E51D71B4EC16EF) @@ -37,13 +42,18 @@ def patch_common_modules(): core.on("asm.block.dbapi.execute", execute_4C9BAC8E228EB347) if asm_config._iast_enabled: _set_metric_iast_instrumented_sink(VULN_PATH_TRAVERSAL) + _is_patched = True def unpatch_common_modules(): + global _is_patched + if not _is_patched: + return try_unwrap("builtins", "open") try_unwrap("urllib.request", "OpenerDirector.open") try_unwrap("_io", "BytesIO.read") try_unwrap("_io", "StringIO.read") + _is_patched = False def wrapped_read_F3E51D71B4EC16EF(original_read_callable, instance, args, kwargs): @@ -78,10 +88,14 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs wrapper for open file function """ if asm_config._iast_enabled: - from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal - - check_and_report_path_traversal(*args, **kwargs) + try: + from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal + check_and_report_path_traversal(*args, **kwargs) + except ImportError: + # open is used during module initialization + # and shouldn't be changed at that time + return original_open_callable(*args, **kwargs) if ( asm_config._asm_enabled and asm_config._ep_enabled diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index b98912b0410..5aab86cf783 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -27,6 +27,7 @@ def wrapped_function(wrapped, instance, args, kwargs): ) return wrapped(*args, **kwargs) """ # noqa: RST201, RST213, RST210 + import inspect import sys @@ -72,6 +73,9 @@ def ddtrace_iast_flask_patch(): sys.modules[module_name] = compiled_code +_iast_propagation_enabled = False + + def enable_iast_propagation(): """Add IAST AST patching in the ModuleWatchdog""" # DEV: These imports are here to avoid _ast.ast_patching import in the top level @@ -79,8 +83,12 @@ def enable_iast_propagation(): from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch from ddtrace.appsec._iast._loader import _exec_iast_patched_module + global _iast_propagation_enabled + if _iast_propagation_enabled: + return log.debug("IAST enabled") ModuleWatchdog.register_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) + _iast_propagation_enabled = True def disable_iast_propagation(): @@ -90,10 +98,14 @@ def disable_iast_propagation(): from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch from ddtrace.appsec._iast._loader import _exec_iast_patched_module + global _iast_propagation_enabled + if not _iast_propagation_enabled: + return try: ModuleWatchdog.remove_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) except KeyError: log.warning("IAST is already disabled and it's not in the ModuleWatchdog") + _iast_propagation_enabled = False __all__ = [ diff --git a/ddtrace/appsec/_remoteconfiguration.py b/ddtrace/appsec/_remoteconfiguration.py index 4aa7910773c..0d470db08c8 100644 --- a/ddtrace/appsec/_remoteconfiguration.py +++ b/ddtrace/appsec/_remoteconfiguration.py @@ -72,6 +72,11 @@ def enable_appsec_rc(test_tracer: Optional[Tracer] = None) -> None: remoteconfig_poller.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking remoteconfig_poller.register(PRODUCTS.ASM, asm_callback) # Exclusion Filters & Custom Rules remoteconfig_poller.register(PRODUCTS.ASM_DD, asm_callback) # DD Rules + # ensure exploit prevention patches are loaded by one-click activation + if asm_config._asm_enabled: + from ddtrace.appsec import load_common_appsec_modules + + load_common_appsec_modules() forksafe.register(_forksafe_appsec_rc) telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True) diff --git a/releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml b/releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml new file mode 100644 index 00000000000..fde9ea25c80 --- /dev/null +++ b/releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + ASM: This fix ensures that common patches for exploit prevention and sca are only loaded if required, and only loaded once. diff --git a/tests/appsec/integrations/test_flask_telemetry.py b/tests/appsec/integrations/test_flask_telemetry.py index 6cea739b94f..99e00112e6f 100644 --- a/tests/appsec/integrations/test_flask_telemetry.py +++ b/tests/appsec/integrations/test_flask_telemetry.py @@ -1,4 +1,5 @@ from ddtrace.appsec._iast._handlers import _on_flask_patch +from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from tests.appsec.appsec_utils import flask_server from tests.utils import override_global_config @@ -24,7 +25,8 @@ def test_flask_instrumented_metrics(telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data metrics_source_tags_result = [metric._tags[0][1] for metric in metrics_result["generate-metrics"]["iast"].values()] - assert len(metrics_source_tags_result) == 7 + assert len(metrics_source_tags_result) == 8 + assert VULN_PATH_TRAVERSAL in metrics_source_tags_result assert origin_to_str(OriginType.HEADER_NAME) in metrics_source_tags_result assert origin_to_str(OriginType.HEADER) in metrics_source_tags_result assert origin_to_str(OriginType.PARAMETER) in metrics_source_tags_result diff --git a/tests/utils.py b/tests/utils.py index 01d8b9aa725..5dd7e554da9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,6 +23,7 @@ from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import http from ddtrace.internal import agent +from ddtrace.internal import core from ddtrace.internal.ci_visibility.writer import CIVisibilityWriter from ddtrace.internal.compat import httplib from ddtrace.internal.compat import parse @@ -181,6 +182,7 @@ def override_global_config(values): # If ddtrace.settings.asm.config has changed, check _asm_can_be_enabled again ddtrace.settings.asm.config._eval_asm_can_be_enabled() try: + core.dispatch("test.config.override") yield finally: # Reset all to their original values From b5b6dd0634d7027d7056239978de2de2abfdb930 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 25 Nov 2024 17:47:46 +0100 Subject: [PATCH 224/372] chore(iast): uncomment google auth package test (#11530) ## Description Uncomment the `google-auth` package test, this time with the `test_import=False` (since it's in the IAST denylist). ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- tests/appsec/iast_packages/test_packages.py | 44 +++++++++++---------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 048fa2abe05..c738eb231b9 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -266,27 +266,29 @@ def uninstall(self, python_cmd): ), PackageForTesting("flask", "2.3.3", "", "", "", test_e2e=False, import_module_to_validate="flask.app"), PackageForTesting("fsspec", "2024.5.0", "", "/", ""), - # PackageForTesting( - # "google-auth", - # "2.35.0", - # "", - # "", - # "", - # import_name="google.auth.crypt.rsa", - # import_module_to_validate="google.auth.crypt.rsa", - # expect_no_change=True, - # ), - # PackageForTesting( - # "google-api-core", - # "2.22.0", - # "", - # "", - # "", - # import_name="google", - # import_module_to_validate="google.auth.iam", - # extras=[("google-cloud-storage", "2.18.2")], - # test_e2e=True, - # ), + PackageForTesting( + "google-auth", + "2.35.0", + "", + "", + "", + test_import=False, + import_name="google.auth.crypt.rsa", + import_module_to_validate="google.auth.crypt.rsa", + expect_no_change=True, + ), + PackageForTesting( + "google-api-core", + "2.22.0", + "", + "", + "", + test_import=False, + import_name="google", + import_module_to_validate="google.auth.iam", + extras=[("google-cloud-storage", "2.18.2")], + test_e2e=True, + ), PackageForTesting( "google-api-python-client", "2.111.0", From 81f52ca75141850e419520bb42cfc87ade4dac7a Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:12:27 -0500 Subject: [PATCH 225/372] chore(tracing): fix some service naming tests that were failing silently (#11483) ## Motivation See silent failures [here](https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/714753885#L6693). DOES NOT NEED TO BE BACKPORTED, FIX WAS ALREADY INCLUDED IN EARLIER BACKPORT PR ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/bottle/trace.py | 2 +- tests/contrib/botocore/test.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/ddtrace/contrib/internal/bottle/trace.py b/ddtrace/contrib/internal/bottle/trace.py index d4748e7fc5f..2f86b1e780a 100644 --- a/ddtrace/contrib/internal/bottle/trace.py +++ b/ddtrace/contrib/internal/bottle/trace.py @@ -22,7 +22,7 @@ class TracePlugin(object): api = 2 def __init__(self, service="bottle", tracer=None, distributed_tracing=None): - self.service = config.service or service + self.service = config._get_service(default=service) self.tracer = tracer or ddtrace.tracer if distributed_tracing is not None: config.bottle.distributed_tracing = distributed_tracing diff --git a/tests/contrib/botocore/test.py b/tests/contrib/botocore/test.py index 4910f32ac46..085dcbbc1e9 100644 --- a/tests/contrib/botocore/test.py +++ b/tests/contrib/botocore/test.py @@ -3776,7 +3776,6 @@ def test_schematized_unspecified_service_secretsmanager_v1(self): assert span.service == DEFAULT_SPAN_SERVICE_NAME assert span.name == "aws.secretsmanager.request" - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_sqs def test_aws_payload_tagging_sqs(self): @@ -3822,7 +3821,6 @@ def test_aws_payload_tagging_sqs(self): trace_in_message = "MessageAttributes" in response["Messages"][0] assert trace_in_message is False - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_sns @mock_sqs @@ -3870,7 +3868,6 @@ def test_aws_payload_tagging_sns(self): # clean up resources sns.delete_topic(TopicArn=topic_arn) - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_sns @mock_sqs @@ -3924,7 +3921,6 @@ def test_aws_payload_tagging_sns_valid_config(self): # clean up resources sns.delete_topic(TopicArn=topic_arn) - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_s3 def test_aws_payload_tagging_s3(self): @@ -3954,7 +3950,6 @@ def test_aws_payload_tagging_s3(self): with pytest.raises(Exception): s3.list_objects(bucket="mybucket") - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_s3 def test_aws_payload_tagging_s3_invalid_config(self): @@ -3973,7 +3968,6 @@ def test_aws_payload_tagging_s3_invalid_config(self): with pytest.raises(Exception): s3.list_objects(bucket="mybucket") - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_s3 def test_aws_payload_tagging_s3_valid_config(self): @@ -3991,7 +3985,6 @@ def test_aws_payload_tagging_s3_valid_config(self): with pytest.raises(Exception): s3.list_objects(bucket="mybucket") - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_events def test_aws_payload_tagging_eventbridge(self): @@ -4034,7 +4027,6 @@ def test_aws_payload_tagging_eventbridge(self): bridge.delete_event_bus(Name="a-test-bus") - @TracerTestCase.run_in_subprocess(env_overrides=dict()) @pytest.mark.snapshot(ignores=snapshot_ignores) @mock_kinesis def test_aws_payload_tagging_kinesis(self): From b9573bed6d2ca4e956c3e20f2ae324651d8c6165 Mon Sep 17 00:00:00 2001 From: Zachary Groves <32471391+ZStriker19@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:06:54 -0600 Subject: [PATCH 226/372] fix(celery): change out.host tags to point to celery broker (#10750) Co-authored-by: wconti27 --- .riot/requirements/118ee6f.txt | 37 ----- .riot/requirements/138c2b7.txt | 35 ----- .riot/requirements/1509aa1.txt | 35 +++++ .riot/requirements/1df4aa0.txt | 35 +++++ .riot/requirements/654f8c0.txt | 38 +++++ .riot/requirements/91a1ee4.txt | 35 ----- ddtrace/contrib/internal/celery/signals.py | 22 ++- ...-celery-out-host-tag-be8da4f2ab88b4cf.yaml | 5 + riotfile.py | 3 +- tests/contrib/celery/base.py | 6 + tests/contrib/celery/test_integration.py | 7 +- tests/contrib/celery/test_tagging.py | 142 ++++++++++++++++++ tests/contrib/jobspec.yml | 2 - 13 files changed, 283 insertions(+), 119 deletions(-) delete mode 100644 .riot/requirements/118ee6f.txt delete mode 100644 .riot/requirements/138c2b7.txt create mode 100644 .riot/requirements/1509aa1.txt create mode 100644 .riot/requirements/1df4aa0.txt create mode 100644 .riot/requirements/654f8c0.txt delete mode 100644 .riot/requirements/91a1ee4.txt create mode 100644 releasenotes/notes/update-celery-out-host-tag-be8da4f2ab88b4cf.yaml create mode 100644 tests/contrib/celery/test_tagging.py diff --git a/.riot/requirements/118ee6f.txt b/.riot/requirements/118ee6f.txt deleted file mode 100644 index d90521f4f87..00000000000 --- a/.riot/requirements/118ee6f.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/118ee6f.in -# -amqp==5.2.0 -attrs==23.1.0 -billiard==4.2.0 -celery==5.3.6 -click==8.1.7 -click-didyoumean==0.3.0 -click-plugins==1.1.1 -click-repl==0.3.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -kombu==5.3.4 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -prompt-toolkit==3.0.43 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -redis==3.5.3 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tzdata==2023.3 -vine==5.1.0 -wcwidth==0.2.12 diff --git a/.riot/requirements/138c2b7.txt b/.riot/requirements/138c2b7.txt deleted file mode 100644 index 91995855c74..00000000000 --- a/.riot/requirements/138c2b7.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/138c2b7.in -# -amqp==5.2.0 -attrs==23.1.0 -billiard==4.2.0 -celery==5.3.6 -click==8.1.7 -click-didyoumean==0.3.0 -click-plugins==1.1.1 -click-repl==0.3.0 -coverage[toml]==7.3.4 -hypothesis==6.45.0 -iniconfig==2.0.0 -kombu==5.3.4 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -prompt-toolkit==3.0.43 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -redis==3.5.3 -six==1.16.0 -sortedcontainers==2.4.0 -tzdata==2023.3 -vine==5.1.0 -wcwidth==0.2.12 diff --git a/.riot/requirements/1509aa1.txt b/.riot/requirements/1509aa1.txt new file mode 100644 index 00000000000..e05463aeb8f --- /dev/null +++ b/.riot/requirements/1509aa1.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1509aa1.in +# +amqp==5.3.1 +attrs==24.2.0 +billiard==4.2.1 +celery[redis]==5.4.0 +click==8.1.7 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 +coverage[toml]==7.6.8 +hypothesis==6.45.0 +iniconfig==2.0.0 +kombu==5.4.2 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +prompt-toolkit==3.0.48 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +redis==5.2.0 +six==1.16.0 +sortedcontainers==2.4.0 +tzdata==2024.2 +vine==5.1.0 +wcwidth==0.2.13 diff --git a/.riot/requirements/1df4aa0.txt b/.riot/requirements/1df4aa0.txt new file mode 100644 index 00000000000..af3e88e2542 --- /dev/null +++ b/.riot/requirements/1df4aa0.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1df4aa0.in +# +amqp==5.3.1 +attrs==24.2.0 +billiard==4.2.1 +celery[redis]==5.4.0 +click==8.1.7 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 +coverage[toml]==7.6.8 +hypothesis==6.45.0 +iniconfig==2.0.0 +kombu==5.4.2 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +prompt-toolkit==3.0.48 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +redis==5.2.0 +six==1.16.0 +sortedcontainers==2.4.0 +tzdata==2024.2 +vine==5.1.0 +wcwidth==0.2.13 diff --git a/.riot/requirements/654f8c0.txt b/.riot/requirements/654f8c0.txt new file mode 100644 index 00000000000..6723fdabd37 --- /dev/null +++ b/.riot/requirements/654f8c0.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/654f8c0.in +# +amqp==5.3.1 +async-timeout==5.0.1 +attrs==24.2.0 +billiard==4.2.1 +celery[redis]==5.4.0 +click==8.1.7 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 +coverage[toml]==7.6.8 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +kombu==5.4.2 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +prompt-toolkit==3.0.48 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +redis==5.2.0 +six==1.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +tzdata==2024.2 +vine==5.1.0 +wcwidth==0.2.13 diff --git a/.riot/requirements/91a1ee4.txt b/.riot/requirements/91a1ee4.txt deleted file mode 100644 index 27ba75912fc..00000000000 --- a/.riot/requirements/91a1ee4.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/91a1ee4.in -# -amqp==5.2.0 -attrs==23.1.0 -billiard==4.2.0 -celery==5.3.6 -click==8.1.7 -click-didyoumean==0.3.0 -click-plugins==1.1.1 -click-repl==0.3.0 -coverage[toml]==7.3.4 -hypothesis==6.45.0 -iniconfig==2.0.0 -kombu==5.3.4 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -prompt-toolkit==3.0.43 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -redis==3.5.3 -six==1.16.0 -sortedcontainers==2.4.0 -tzdata==2023.3 -vine==5.1.0 -wcwidth==0.2.12 diff --git a/ddtrace/contrib/internal/celery/signals.py b/ddtrace/contrib/internal/celery/signals.py index e235d6efb3b..76f07ee7524 100644 --- a/ddtrace/contrib/internal/celery/signals.py +++ b/ddtrace/contrib/internal/celery/signals.py @@ -1,5 +1,7 @@ +from urllib.parse import urlparse + +from celery import current_app from celery import registry -from celery.utils import nodenames from ddtrace import Pin from ddtrace import config @@ -181,9 +183,21 @@ def trace_after_publish(*args, **kwargs): if span is None: return else: - nodename = span.get_tag("celery.hostname") - if nodename is not None: - _, host = nodenames.nodesplit(nodename) + broker_url = current_app.conf.broker_url + + if broker_url == "memory://": + host = broker_url + else: + parsed_url = urlparse(broker_url) + + host = None + if parsed_url.hostname: + host = parsed_url.hostname + + if parsed_url.port: + span.set_metric(net.TARGET_PORT, parsed_url.port) + + if host: span.set_tag_str(net.TARGET_HOST, host) span.finish() diff --git a/releasenotes/notes/update-celery-out-host-tag-be8da4f2ab88b4cf.yaml b/releasenotes/notes/update-celery-out-host-tag-be8da4f2ab88b4cf.yaml new file mode 100644 index 00000000000..3bb4841c9bd --- /dev/null +++ b/releasenotes/notes/update-celery-out-host-tag-be8da4f2ab88b4cf.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + celery: Changes celery ``out.host`` span tag to point towards broker host url instead of local celery process hostname. Fixes + inferred service representation issues when using celery. diff --git a/riotfile.py b/riotfile.py index d7b9a081269..2133641a05c 100644 --- a/riotfile.py +++ b/riotfile.py @@ -720,10 +720,9 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "PYTEST_PLUGINS": "celery.contrib.pytest", }, pkgs={ - "celery": [ + "celery[redis]": [ latest, ], - "redis": "~=3.5", }, ), ], diff --git a/tests/contrib/celery/base.py b/tests/contrib/celery/base.py index 46055bc6070..3a14f92c33f 100644 --- a/tests/contrib/celery/base.py +++ b/tests/contrib/celery/base.py @@ -8,6 +8,7 @@ from ddtrace.contrib.celery import unpatch from tests.utils import TracerTestCase +from ..config import RABBITMQ_CONFIG from ..config import REDIS_CONFIG @@ -15,6 +16,11 @@ BROKER_URL = "{redis}/{db}".format(redis=REDIS_URL, db=0) BACKEND_URL = "{redis}/{db}".format(redis=REDIS_URL, db=1) +AMQP_URL = "amqp://{user}:{password}@127.0.0.1:{port}".format( + user=RABBITMQ_CONFIG["user"], password=RABBITMQ_CONFIG["password"], port=RABBITMQ_CONFIG["port"] +) +AMQP_BROKER_URL = "{amqp}//".format(amqp=AMQP_URL) + @pytest.fixture(scope="session") def celery_config(): diff --git a/tests/contrib/celery/test_integration.py b/tests/contrib/celery/test_integration.py index 03276a79296..c3696f27307 100644 --- a/tests/contrib/celery/test_integration.py +++ b/tests/contrib/celery/test_integration.py @@ -1,6 +1,5 @@ from collections import Counter import os -import socket import subprocess from time import sleep @@ -194,7 +193,7 @@ def fn_task_parameters(user, force_logout=False): assert async_span.get_tag("celery.routing_key") == "celery" assert async_span.get_tag("component") == "celery" assert async_span.get_tag("span.kind") == "producer" - assert async_span.get_tag("out.host") == socket.gethostname() + assert async_span.get_tag("out.host") == "memory://" else: assert 1 == len(traces) assert 1 == len(traces[0]) @@ -239,7 +238,7 @@ def fn_task_parameters(user, force_logout=False): assert async_span.get_tag("celery.routing_key") == "celery" assert async_span.get_tag("component") == "celery" assert async_span.get_tag("span.kind") == "producer" - assert async_span.get_tag("out.host") == socket.gethostname() + assert async_span.get_tag("out.host") == "memory://" else: assert 1 == len(traces) assert 1 == len(traces[0]) @@ -635,7 +634,7 @@ def fn_task_parameters(user, force_logout=False): assert async_span.get_tag("celery.routing_key") == "celery" assert async_span.get_tag("component") == "celery" assert async_span.get_tag("span.kind") == "producer" - assert async_span.get_tag("out.host") == socket.gethostname() + assert async_span.get_tag("out.host") == "memory://" run_span = self.find_span(name="celery.run") assert run_span.name == "celery.run" diff --git a/tests/contrib/celery/test_tagging.py b/tests/contrib/celery/test_tagging.py new file mode 100644 index 00000000000..af40c4f9209 --- /dev/null +++ b/tests/contrib/celery/test_tagging.py @@ -0,0 +1,142 @@ +import socket +import time + +from celery import Celery +from celery.contrib.testing.worker import start_worker +import pytest + +from ddtrace import Pin +from ddtrace.contrib.celery import patch +from ddtrace.contrib.celery import unpatch +from tests.utils import DummyTracer + +from .base import AMQP_BROKER_URL +from .base import BACKEND_URL +from .base import BROKER_URL + + +redis_celery_app = Celery( + "mul_celery", + broker=BROKER_URL, + backend=BACKEND_URL, +) + + +@redis_celery_app.task +def multiply(x, y): + return x * y + + +amqp_celery_app = Celery( + "add_celery", + broker=AMQP_BROKER_URL, + backend="rpc://", +) + + +@amqp_celery_app.task +def add(x, y): + return x + y + + +@pytest.fixture(autouse=False) +def instrument_celery(): + # Instrument Celery and create an app with Broker and Result backends + patch() + yield + # Remove instrumentation from Celery + unpatch() + + +@pytest.fixture(scope="session") +def celery_config(): + return {"broker_url": BROKER_URL} + + +@pytest.fixture +def dummy_tracer(): + return DummyTracer() + + +@pytest.fixture(autouse=False) +def traced_redis_celery_app(instrument_celery, dummy_tracer): + Pin.get_from(redis_celery_app) + Pin.override(redis_celery_app, tracer=dummy_tracer) + yield redis_celery_app + + +@pytest.fixture(autouse=False) +def traced_amqp_celery_app(instrument_celery, dummy_tracer): + Pin.get_from(amqp_celery_app) + Pin.override(amqp_celery_app, tracer=dummy_tracer) + yield amqp_celery_app + + +def test_redis_task(traced_redis_celery_app): + tracer = Pin.get_from(traced_redis_celery_app).tracer + + with start_worker( + traced_redis_celery_app, + pool="solo", + loglevel="info", + perform_ping_check=False, + shutdown_timeout=30, + ): + t = multiply.delay(4, 4) + assert t.get(timeout=2) == 16 + + # wait for spans to be received + time.sleep(3) + + assert_traces(tracer, "multiply", t, 6379) + + +def test_amqp_task(instrument_celery, traced_amqp_celery_app): + tracer = Pin.get_from(traced_amqp_celery_app).tracer + + with start_worker( + traced_amqp_celery_app, + pool="solo", + loglevel="info", + perform_ping_check=False, + shutdown_timeout=30, + ): + t = add.delay(4, 4) + assert t.get(timeout=2) == 8 + + # wait for spans to be received + time.sleep(3) + + assert_traces(tracer, "add", t, 5672) + + +def assert_traces(tracer, task_name, task, port): + traces = tracer.pop_traces() + + assert 2 == len(traces) + assert 1 == len(traces[0]) + assert 1 == len(traces[1]) + async_span = traces[0][0] + run_span = traces[1][0] + + assert async_span.error == 0 + assert async_span.name == "celery.apply" + assert async_span.resource == f"tests.contrib.celery.test_tagging.{task_name}" + assert async_span.service == "celery-producer" + assert async_span.get_tag("celery.id") == task.task_id + assert async_span.get_tag("celery.action") == "apply_async" + assert async_span.get_tag("celery.routing_key") == "celery" + assert async_span.get_tag("component") == "celery" + assert async_span.get_tag("span.kind") == "producer" + assert async_span.get_tag("out.host") == "127.0.0.1" + assert async_span.get_metric("network.destination.port") == port + + assert run_span.error == 0 + assert run_span.name == "celery.run" + assert run_span.resource == f"tests.contrib.celery.test_tagging.{task_name}" + assert run_span.service == "celery-worker" + assert run_span.get_tag("celery.id") == task.task_id + assert run_span.get_tag("celery.action") == "run" + assert run_span.get_tag("component") == "celery" + assert run_span.get_tag("span.kind") == "consumer" + assert socket.gethostname() in run_span.get_tag("celery.hostname") diff --git a/tests/contrib/jobspec.yml b/tests/contrib/jobspec.yml index a4695b39f77..6cf1538eff3 100644 --- a/tests/contrib/jobspec.yml +++ b/tests/contrib/jobspec.yml @@ -49,9 +49,7 @@ celery: env: SUITE_NAME: celery LOG_LEVEL: DEBUG - SNAPSHOT_DIR: /snapshots PORT: 9126 - SNAPSHOT_CI: 1 DD_POOL_TRACE_CHECK_FAILURES: true DD_DISABLE_ERROR_RESPONSES: true ENABLED_CHECKS: trace_stall,meta_tracer_version_header,trace_content_length,trace_peer_service,trace_dd_service # disable flaky content length check From 7dd3965e61d27fa8adefb3e9b2267c8a2161b16d Mon Sep 17 00:00:00 2001 From: Charles de Beauchesne Date: Tue, 26 Nov 2024 15:08:13 +0100 Subject: [PATCH 227/372] chore(ci): remove legacy name from images to prune (#11544) This name does not exists anymore, and makes the CI fail for now 3 months https://github.com/DataDog/dd-trace-py/actions/workflows/lib-inject-prune.yml ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/lib-inject-prune.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lib-inject-prune.yml b/.github/workflows/lib-inject-prune.yml index 132a3c87438..02b7e6d1728 100644 --- a/.github/workflows/lib-inject-prune.yml +++ b/.github/workflows/lib-inject-prune.yml @@ -41,7 +41,6 @@ jobs: - 'dd-lib-python-init-test-django-uwsgi' - 'dd-lib-python-init-test-app' - 'dd-python-agent-init' - - 'dd-lib-python-init-test-protobuf-old' steps: - name: Prune registry uses: vlaurin/action-ghcr-prune@0a539594d122b915e71c59733a5b115bfaaf5d52 #v0.5.0 From 5dfd7f07311d3dc7cfc7778c2756ef9626dc5d0e Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:44:55 +0000 Subject: [PATCH 228/372] chore(ci_visibility): fix telemetry for events and API requests (#11545) This fixes some internal telemetry that was using enum names instead of their value, which was creating metrics or tags with the wrong names. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/_api_client.py | 23 ++++++++-------- ddtrace/internal/ci_visibility/recorder.py | 9 +++---- .../telemetry/early_flake_detection.py | 7 +++-- .../ci_visibility/telemetry/events.py | 27 ++++++++++++++++--- .../internal/ci_visibility/telemetry/git.py | 16 ++++++----- ddtrace/internal/ci_visibility/utils.py | 8 ++++++ 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/ddtrace/internal/ci_visibility/_api_client.py b/ddtrace/internal/ci_visibility/_api_client.py index e244d94ed0a..bf01d8e9b38 100644 --- a/ddtrace/internal/ci_visibility/_api_client.py +++ b/ddtrace/internal/ci_visibility/_api_client.py @@ -321,10 +321,10 @@ def fetch_settings(self) -> TestVisibilityAPISettings: """ metric_names = APIRequestMetricNames( - count=GIT_TELEMETRY.SETTINGS_COUNT, - duration=GIT_TELEMETRY.SETTINGS_MS, + count=GIT_TELEMETRY.SETTINGS_COUNT.value, + duration=GIT_TELEMETRY.SETTINGS_MS.value, response_bytes=None, - error=GIT_TELEMETRY.SETTINGS_ERRORS, + error=GIT_TELEMETRY.SETTINGS_ERRORS.value, ) payload = { @@ -390,6 +390,7 @@ def fetch_settings(self) -> TestVisibilityAPISettings: api_settings.skipping_enabled, api_settings.require_git, api_settings.itr_enabled, + api_settings.flaky_test_retries_enabled, api_settings.early_flake_detection.enabled, ) @@ -402,10 +403,10 @@ def fetch_skippable_items( timeout = DEFAULT_ITR_SKIPPABLE_TIMEOUT metric_names = APIRequestMetricNames( - count=SKIPPABLE_TESTS_TELEMETRY.REQUEST, - duration=SKIPPABLE_TESTS_TELEMETRY.REQUEST_MS, - response_bytes=SKIPPABLE_TESTS_TELEMETRY.RESPONSE_BYTES, - error=SKIPPABLE_TESTS_TELEMETRY.REQUEST_ERRORS, + count=SKIPPABLE_TESTS_TELEMETRY.REQUEST.value, + duration=SKIPPABLE_TESTS_TELEMETRY.REQUEST_MS.value, + response_bytes=SKIPPABLE_TESTS_TELEMETRY.RESPONSE_BYTES.value, + error=SKIPPABLE_TESTS_TELEMETRY.REQUEST_ERRORS.value, ) payload = { @@ -470,10 +471,10 @@ def fetch_skippable_items( def fetch_unique_tests(self) -> t.Optional[t.Set[InternalTestId]]: metric_names = APIRequestMetricNames( - count=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST, - duration=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST_MS, - response_bytes=EARLY_FLAKE_DETECTION_TELEMETRY.RESPONSE_BYTES, - error=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST_ERRORS, + count=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST.value, + duration=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST_MS.value, + response_bytes=EARLY_FLAKE_DETECTION_TELEMETRY.RESPONSE_BYTES.value, + error=EARLY_FLAKE_DETECTION_TELEMETRY.REQUEST_ERRORS.value, ) unique_test_ids: t.Set[InternalTestId] = set() diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index d6c2634ed6a..12bb3688dad 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -65,7 +65,7 @@ from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient from ddtrace.internal.ci_visibility.git_data import GitData from ddtrace.internal.ci_visibility.git_data import get_git_data_from_tags -from ddtrace.internal.ci_visibility.telemetry.constants import TEST_FRAMEWORKS +from ddtrace.internal.ci_visibility.utils import _get_test_framework_telemetry_name from ddtrace.internal.ci_visibility.writer import CIVisibilityEventClient from ddtrace.internal.ci_visibility.writer import CIVisibilityWriter from ddtrace.internal.codeowners import Codeowners @@ -910,9 +910,7 @@ def wrapper(*args, **kwargs): @_requires_civisibility_enabled -def _on_discover_session( - discover_args: TestSession.DiscoverArgs, test_framework_telemetry_name: Optional[TEST_FRAMEWORKS] = None -): +def _on_discover_session(discover_args: TestSession.DiscoverArgs): log.debug("Handling session discovery") # _requires_civisibility_enabled prevents us from getting here, but this makes type checkers happy @@ -928,7 +926,8 @@ def _on_discover_session( # If we're not provided a root directory, try and extract it from workspace, defaulting to CWD workspace_path = discover_args.root_dir or Path(CIVisibility.get_workspace_path() or os.getcwd()) - test_framework_telemetry_name = test_framework_telemetry_name or TEST_FRAMEWORKS.MANUAL + # Prevent high cardinality of test framework telemetry tag by matching with known frameworks + test_framework_telemetry_name = _get_test_framework_telemetry_name(discover_args.test_framework) efd_api_settings = CIVisibility.get_efd_api_settings() if efd_api_settings is None or not CIVisibility.is_efd_enabled(): diff --git a/ddtrace/internal/ci_visibility/telemetry/early_flake_detection.py b/ddtrace/internal/ci_visibility/telemetry/early_flake_detection.py index 7c9bf9dd916..f8a512e7048 100644 --- a/ddtrace/internal/ci_visibility/telemetry/early_flake_detection.py +++ b/ddtrace/internal/ci_visibility/telemetry/early_flake_detection.py @@ -7,9 +7,6 @@ log = get_logger(__name__) -EARLY_FLAKE_DETECTION_TELEMETRY_PREFIX = "early_flake_detection." -RESPONSE_TESTS = f"{EARLY_FLAKE_DETECTION_TELEMETRY_PREFIX}response_tests" - class EARLY_FLAKE_DETECTION_TELEMETRY(str, Enum): REQUEST = "early_flake_detection.request" @@ -21,4 +18,6 @@ class EARLY_FLAKE_DETECTION_TELEMETRY(str, Enum): def record_early_flake_detection_tests_count(early_flake_detection_count: int): log.debug("Recording early flake detection tests count telemetry: %s", early_flake_detection_count) - telemetry_writer.add_count_metric(_NAMESPACE, RESPONSE_TESTS, early_flake_detection_count) + telemetry_writer.add_distribution_metric( + _NAMESPACE, EARLY_FLAKE_DETECTION_TELEMETRY.RESPONSE_TESTS.value, early_flake_detection_count + ) diff --git a/ddtrace/internal/ci_visibility/telemetry/events.py b/ddtrace/internal/ci_visibility/telemetry/events.py index 189c9a838c2..bdaee8a9384 100644 --- a/ddtrace/internal/ci_visibility/telemetry/events.py +++ b/ddtrace/internal/ci_visibility/telemetry/events.py @@ -31,6 +31,27 @@ def _record_event( is_retry: Optional[bool] = False, early_flake_detection_abort_reason: Optional[str] = None, ): + log.debug( + "Recording event telemetry: event=%s" + ", event_type=%s" + ", test_framework=%s" + ", has_codeowners=%s" + ", is_unsuported_ci=%s" + ", is_benchmark=%s" + ", is_new=%s" + ", is_retry=%s" + ", early_flake_detection_abort_reason=%s", + event, + event_type, + test_framework, + has_codeowners, + is_unsupported_ci, + is_benchmark, + is_new, + is_retry, + early_flake_detection_abort_reason, + ) + if has_codeowners and event_type != EVENT_TYPES.SESSION: log.debug("has_codeowners tag can only be set for sessions, but event type is %s", event_type) if is_unsupported_ci and event_type != EVENT_TYPES.SESSION: @@ -52,9 +73,9 @@ def _record_event( "early_flake_detection_abort_reason tag can only be set for tests and session finish events", ) - _tags: List[Tuple[str, str]] = [("event_type", event_type)] + _tags: List[Tuple[str, str]] = [("event_type", event_type.value)] if test_framework and test_framework != TEST_FRAMEWORKS.MANUAL: - _tags.append(("test_framework", test_framework)) + _tags.append(("test_framework", str(test_framework.value))) if event_type == EVENT_TYPES.SESSION: _tags.append(("has_codeowners", "1" if has_codeowners else "0")) _tags.append(("is_unsupported_ci", "1" if has_codeowners else "0")) @@ -74,7 +95,7 @@ def _record_event( ): _tags.append(("early_flake_detection_abort_reason", early_flake_detection_abort_reason)) - telemetry_writer.add_count_metric(_NAMESPACE, event, 1, tuple(_tags)) + telemetry_writer.add_count_metric(_NAMESPACE, event.value, 1, tuple(_tags)) def record_event_created( diff --git a/ddtrace/internal/ci_visibility/telemetry/git.py b/ddtrace/internal/ci_visibility/telemetry/git.py index fa4b2e87285..12136ce14fe 100644 --- a/ddtrace/internal/ci_visibility/telemetry/git.py +++ b/ddtrace/internal/ci_visibility/telemetry/git.py @@ -51,27 +51,31 @@ def record_settings_response( require_git: Optional[bool] = False, itr_enabled: Optional[bool] = False, early_flake_detection_enabled: Optional[bool] = False, + flaky_test_retries_enabled: Optional[bool] = False, ) -> None: log.debug( - "Recording settings telemetry: %s, %s, %s, %s, %s", + "Recording settings telemetry: %s, %s, %s, %s, %s, %s", coverage_enabled, skipping_enabled, require_git, itr_enabled, early_flake_detection_enabled, + flaky_test_retries_enabled, ) # Telemetry "booleans" are true if they exist, otherwise false response_tags = [] if coverage_enabled: - response_tags.append(("coverage_enabled", "1")) + response_tags.append(("coverage_enabled", "true")) if skipping_enabled: - response_tags.append(("itrskip_enabled", "1")) + response_tags.append(("itrskip_enabled", "true")) if require_git: - response_tags.append(("require_git", "1")) + response_tags.append(("require_git", "true")) if itr_enabled: - response_tags.append(("itr_enabled", "1")) + response_tags.append(("itr_enabled", "true")) if early_flake_detection_enabled: - response_tags.append(("early_flake_detection_enabled", "1")) + response_tags.append(("early_flake_detection_enabled", "true")) + if flaky_test_retries_enabled: + response_tags.append(("flaky_test_retries_enabled", "true")) if response_tags: telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_RESPONSE, 1, tuple(response_tags)) diff --git a/ddtrace/internal/ci_visibility/utils.py b/ddtrace/internal/ci_visibility/utils.py index 248b90a6984..d111305e317 100644 --- a/ddtrace/internal/ci_visibility/utils.py +++ b/ddtrace/internal/ci_visibility/utils.py @@ -9,6 +9,7 @@ from ddtrace.contrib.internal.coverage.constants import PCT_COVERED_KEY from ddtrace.ext import test from ddtrace.internal.ci_visibility.constants import CIVISIBILITY_LOG_FILTER_RE +from ddtrace.internal.ci_visibility.telemetry.constants import TEST_FRAMEWORKS from ddtrace.internal.logger import get_logger @@ -147,3 +148,10 @@ def combine_url_path(*args: str): NOTE: this is custom-built for its current usage in the Test Visibility codebase. Use with care. """ return "/".join(str(segment).strip("/") for segment in args) + + +def _get_test_framework_telemetry_name(test_framework: str) -> TEST_FRAMEWORKS: + for framework in TEST_FRAMEWORKS: + if framework.value == test_framework: + return framework + return TEST_FRAMEWORKS.MANUAL From 5ac21cc866b30315fea93613c07dd9a38a9f9b38 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Tue, 26 Nov 2024 11:13:01 -0500 Subject: [PATCH 229/372] chore(ci): update to run gitlab test suites on any lockfile change (#11536) When the requirement lockfiles are updated, the test suite corresponding to the riot hash / venv is not always run. As a workaround, this modifies the logic to trigger all test suites when a lockfile is updated. In the future, we can modify this logic to be more fine-grained and capture only the relevant test suites for the modified lockfiles. Example test PR that triggers all circleci and gitlab test suites to run: #11541 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Gabriele N. Tornetta --- tests/.suitespec.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/.suitespec.json b/tests/.suitespec.json index 8ec302b9f5e..2b58ced3344 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -9,6 +9,7 @@ "docker/*", "docker-compose.yml", "riotfile.py", + ".riot/requirements/*", "scripts/ddtest", "scripts/run-test-suite", "hatch.toml", From a09802938989ce42d55afcda637f5ca917a70022 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 26 Nov 2024 16:21:42 +0000 Subject: [PATCH 230/372] perf(cos): cache user code checks (#11527) The creation of Path objects in CPython < 3.11 is known to be slow and inefficient. We implement an overload of the helper function that takes a plain string as argument and caches the results to avoid creating Path objects repeatedly. ## Before Screenshot 2024-11-25 at 14 31 36 ## After Screenshot 2024-11-25 at 14 32 52 ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_origin/span.py | 2 +- ddtrace/internal/packages.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index 9f0433cbca0..bd3744c20f2 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -209,7 +209,7 @@ def on_span_start(self, span: Span) -> None: code = frame.f_code filename = code.co_filename - if is_user_code(Path(filename)): + if is_user_code(filename): n = next(seq) if n >= co_config.max_user_frames: break diff --git a/ddtrace/internal/packages.py b/ddtrace/internal/packages.py index e256200ec7a..8b369b9709c 100644 --- a/ddtrace/internal/packages.py +++ b/ddtrace/internal/packages.py @@ -1,5 +1,6 @@ import collections from functools import lru_cache as cached +from functools import singledispatch import inspect import logging from os import fspath # noqa:F401 @@ -241,10 +242,24 @@ def is_third_party(path: Path) -> bool: return package.name in _third_party_packages() -def is_user_code(path: Path) -> bool: +@singledispatch +def is_user_code(path) -> bool: + raise NotImplementedError(f"Unsupported type {type(path)}") + + +@is_user_code.register +def _(path: Path) -> bool: return not (is_stdlib(path) or is_third_party(path)) +# DEV: Creating Path objects on Python < 3.11 is expensive +@is_user_code.register(str) +@cached(maxsize=1024) +def _(path: str) -> bool: + _path = Path(path) + return not (is_stdlib(_path) or is_third_party(_path)) + + @cached(maxsize=256) def is_distribution_available(name: str) -> bool: """Determine if a distribution is available in the current environment.""" From 935454afce36052b64a5c1e2ee5f4a2236fa1ba4 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:00:26 +0000 Subject: [PATCH 231/372] chore(ci_visibility): fix API telemetry and make ordering stable with better logging (#11549) Follow-up to #11545 that was missed in the last PR. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/ci_visibility/_api_client.py | 12 ++++++------ ddtrace/internal/ci_visibility/telemetry/git.py | 16 +++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/ddtrace/internal/ci_visibility/_api_client.py b/ddtrace/internal/ci_visibility/_api_client.py index bf01d8e9b38..aaeaa59f1d7 100644 --- a/ddtrace/internal/ci_visibility/_api_client.py +++ b/ddtrace/internal/ci_visibility/_api_client.py @@ -386,12 +386,12 @@ def fetch_settings(self) -> TestVisibilityAPISettings: ) record_settings_response( - api_settings.coverage_enabled, - api_settings.skipping_enabled, - api_settings.require_git, - api_settings.itr_enabled, - api_settings.flaky_test_retries_enabled, - api_settings.early_flake_detection.enabled, + coverage_enabled=api_settings.coverage_enabled, + skipping_enabled=api_settings.skipping_enabled, + require_git=api_settings.require_git, + itr_enabled=api_settings.itr_enabled, + flaky_test_retries_enabled=api_settings.flaky_test_retries_enabled, + early_flake_detection_enabled=api_settings.early_flake_detection.enabled, ) return api_settings diff --git a/ddtrace/internal/ci_visibility/telemetry/git.py b/ddtrace/internal/ci_visibility/telemetry/git.py index 12136ce14fe..761ecc62a00 100644 --- a/ddtrace/internal/ci_visibility/telemetry/git.py +++ b/ddtrace/internal/ci_visibility/telemetry/git.py @@ -50,17 +50,23 @@ def record_settings_response( skipping_enabled: Optional[bool] = False, require_git: Optional[bool] = False, itr_enabled: Optional[bool] = False, - early_flake_detection_enabled: Optional[bool] = False, flaky_test_retries_enabled: Optional[bool] = False, + early_flake_detection_enabled: Optional[bool] = False, ) -> None: log.debug( - "Recording settings telemetry: %s, %s, %s, %s, %s, %s", + "Recording settings telemetry:" + " coverage_enabled=%s" + ", skipping_enabled=%s" + ", require_git=%s" + ", itr_enabled=%s" + ", flaky_test_retries_enabled=%s" + ", early_flake_detection_enabled=%s", coverage_enabled, skipping_enabled, require_git, itr_enabled, - early_flake_detection_enabled, flaky_test_retries_enabled, + early_flake_detection_enabled, ) # Telemetry "booleans" are true if they exist, otherwise false response_tags = [] @@ -72,10 +78,10 @@ def record_settings_response( response_tags.append(("require_git", "true")) if itr_enabled: response_tags.append(("itr_enabled", "true")) - if early_flake_detection_enabled: - response_tags.append(("early_flake_detection_enabled", "true")) if flaky_test_retries_enabled: response_tags.append(("flaky_test_retries_enabled", "true")) + if early_flake_detection_enabled: + response_tags.append(("early_flake_detection_enabled", "true")) if response_tags: telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_RESPONSE, 1, tuple(response_tags)) From cd1eba4290e54d41fe5a4f4e9769bec9f225d28d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:35:54 -0500 Subject: [PATCH 232/372] chore: update redis latest version to 5.2.0 (#11429) Update redis lockfiles and dependency package lockfiles. This performs the following updates: 1) Some redis lockfiles use redis `latest`. This will update redis and dependencies. 2) Some redis lockfiles use a pinned (non-latest) version of redis, but require the `latest` version of another package. This will update all such packages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Quinna Halim --- .riot/requirements/177912e.txt | 12 ++++++------ .riot/requirements/181128c.txt | 14 +++++++------- .riot/requirements/18b32f4.txt | 8 ++++---- .riot/requirements/1916976.txt | 14 +++++++------- .riot/requirements/195ecad.txt | 12 ++++++------ .riot/requirements/1d32f58.txt | 14 +++++++------- .riot/requirements/315c2cb.txt | 6 +++--- .riot/requirements/74e07bf.txt | 12 ++++++------ .riot/requirements/9232661.txt | 10 +++++----- .riot/requirements/baf46ab.txt | 6 +++--- .riot/requirements/c961281.txt | 8 ++++---- .riot/requirements/f4b1bd3.txt | 6 +++--- 12 files changed, 61 insertions(+), 61 deletions(-) diff --git a/.riot/requirements/177912e.txt b/.riot/requirements/177912e.txt index 41ae45d3e58..2f9078064b0 100644 --- a/.riot/requirements/177912e.txt +++ b/.riot/requirements/177912e.txt @@ -4,21 +4,21 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/177912e.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==5.0.1 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 diff --git a/.riot/requirements/181128c.txt b/.riot/requirements/181128c.txt index 5f601f7256a..352635e1598 100644 --- a/.riot/requirements/181128c.txt +++ b/.riot/requirements/181128c.txt @@ -4,23 +4,23 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/181128c.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==5.0.1 sortedcontainers==2.4.0 -tomli==2.0.2 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/18b32f4.txt b/.riot/requirements/18b32f4.txt index ee6c10ee402..8715febbb83 100644 --- a/.riot/requirements/18b32f4.txt +++ b/.riot/requirements/18b32f4.txt @@ -5,17 +5,17 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/18b32f4.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==5.0.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1916976.txt b/.riot/requirements/1916976.txt index 11f5e647fb0..a4b7f6cacea 100644 --- a/.riot/requirements/1916976.txt +++ b/.riot/requirements/1916976.txt @@ -4,23 +4,23 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1916976.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/195ecad.txt b/.riot/requirements/195ecad.txt index 984f240c0e5..5a34b51b95e 100644 --- a/.riot/requirements/195ecad.txt +++ b/.riot/requirements/195ecad.txt @@ -4,21 +4,21 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/195ecad.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 diff --git a/.riot/requirements/1d32f58.txt b/.riot/requirements/1d32f58.txt index 6574a8ae4b2..1767e16a9e7 100644 --- a/.riot/requirements/1d32f58.txt +++ b/.riot/requirements/1d32f58.txt @@ -4,23 +4,23 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d32f58.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 -zipp==3.20.2 +tomli==2.1.0 +zipp==3.21.0 diff --git a/.riot/requirements/315c2cb.txt b/.riot/requirements/315c2cb.txt index 3c1b0f30c8f..8a45f9b13fe 100644 --- a/.riot/requirements/315c2cb.txt +++ b/.riot/requirements/315c2cb.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/315c2cb.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 @@ -13,7 +13,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 @@ -22,5 +22,5 @@ pytest-mock==3.14.0 pytest-randomly==3.15.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/74e07bf.txt b/.riot/requirements/74e07bf.txt index f80d2c91be7..b2fa8feb3ae 100644 --- a/.riot/requirements/74e07bf.txt +++ b/.riot/requirements/74e07bf.txt @@ -4,21 +4,21 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/74e07bf.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 diff --git a/.riot/requirements/9232661.txt b/.riot/requirements/9232661.txt index f4ce839d3bb..88ef74ad89d 100644 --- a/.riot/requirements/9232661.txt +++ b/.riot/requirements/9232661.txt @@ -5,17 +5,17 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/9232661.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==5.1.1 +pytest-randomly==3.16.0 +redis==5.2.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/baf46ab.txt b/.riot/requirements/baf46ab.txt index 7e6602cef35..5a983e008c5 100644 --- a/.riot/requirements/baf46ab.txt +++ b/.riot/requirements/baf46ab.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/baf46ab.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 @@ -13,7 +13,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 @@ -22,5 +22,5 @@ pytest-mock==3.14.0 pytest-randomly==3.15.0 redis==4.6.0 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 zipp==3.20.2 diff --git a/.riot/requirements/c961281.txt b/.riot/requirements/c961281.txt index cb412a89b47..63d4461b284 100644 --- a/.riot/requirements/c961281.txt +++ b/.riot/requirements/c961281.txt @@ -5,17 +5,17 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/c961281.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.7 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 redis==4.6.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/f4b1bd3.txt b/.riot/requirements/f4b1bd3.txt index eadbf79ff0f..da3d86a840f 100644 --- a/.riot/requirements/f4b1bd3.txt +++ b/.riot/requirements/f4b1bd3.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/f4b1bd3.in # -async-timeout==4.0.3 +async-timeout==5.0.1 attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 @@ -13,7 +13,7 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pytest==8.3.3 pytest-asyncio==0.23.7 @@ -22,5 +22,5 @@ pytest-mock==3.14.0 pytest-randomly==3.15.0 redis==5.0.1 sortedcontainers==2.4.0 -tomli==2.0.2 +tomli==2.1.0 zipp==3.20.2 From 3fabd0a66886cf8b27b60b49485a50234cf7f72f Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:52:16 -0500 Subject: [PATCH 233/372] feat(vertexai): add release note for vertex ai apm tracing (#11548) Add missing release note for this [PR](https://github.com/DataDog/dd-trace-py/pull/11236). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- releasenotes/notes/feat-vertexai-e4a6dbe95d19eee0.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/feat-vertexai-e4a6dbe95d19eee0.yaml diff --git a/releasenotes/notes/feat-vertexai-e4a6dbe95d19eee0.yaml b/releasenotes/notes/feat-vertexai-e4a6dbe95d19eee0.yaml new file mode 100644 index 00000000000..b94f63a5f37 --- /dev/null +++ b/releasenotes/notes/feat-vertexai-e4a6dbe95d19eee0.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + vertexai: Introduces tracing support for Google's Vertex AI SDK for Python's ``generate_content`` and ``send_message`` calls. + See `the docs `_ + for more information. From 9c9fe4d48e309dc12df06c5e5099ce472abb8f91 Mon Sep 17 00:00:00 2001 From: kyle Date: Tue, 26 Nov 2024 16:41:55 -0500 Subject: [PATCH 234/372] chore(tests): fix tz warning (#11497) Running the tests locally, I get: ``` DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC ``` The recommended upgrade is to use `datetime.now(timezone.utc)` which is what is done here. --- tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 5dd7e554da9..de0129f75a3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1339,7 +1339,7 @@ def _should_skip(condition=None, until: int = None): until = dt.datetime(3000, 1, 1) else: until = dt.datetime.fromtimestamp(until) - if until and dt.datetime.utcnow() < until.replace(tzinfo=None): + if until and dt.datetime.now(dt.timezone.utc).replace(tzinfo=None) < until.replace(tzinfo=None): return True if condition is not None and not condition: return False From d386312eba6154c6cf73d558cd465ff0a064bae8 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 26 Nov 2024 21:48:58 +0000 Subject: [PATCH 235/372] ci: merge suitespec and jobspec (#11401) We merge the suitespec and jobspec into a unique, modular solution that contains all the information to dynamically generate CI jobs based on the changes contained in a PR. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 103 -- .github/CODEOWNERS | 2 +- .gitlab/tests.yml | 13 - docs/contributing-integrations.rst | 4 +- docs/contributing-testing.rst | 20 +- docs/spelling_wordlist.txt | 3 +- hatch.toml | 2 + scripts/gen_circleci_config.py | 19 +- scripts/gen_gitlab_config.py | 150 ++- scripts/needs_testrun.py | 1 + tests/.suitespec.json | 1529 ---------------------------- tests/README.md | 87 +- tests/appsec/jobspec.yml | 65 -- tests/appsec/suitespec.yml | 173 ++++ tests/ci_visibility/jobspec.yml | 29 - tests/ci_visibility/suitespec.yml | 82 ++ tests/contrib/jobspec.yml | 358 ------- tests/contrib/suitespec.yml | 1152 +++++++++++++++++++++ tests/debugging/jobspec.yml | 5 - tests/debugging/suitespec.yml | 17 + tests/jobspec.yml | 46 - tests/llmobs/jobspec.yml | 32 - tests/llmobs/suitespec.yml | 94 ++ tests/suitespec.py | 48 +- tests/suitespec.yml | 255 +++++ 25 files changed, 1996 insertions(+), 2293 deletions(-) delete mode 100644 tests/.suitespec.json delete mode 100644 tests/appsec/jobspec.yml create mode 100644 tests/appsec/suitespec.yml delete mode 100644 tests/ci_visibility/jobspec.yml create mode 100644 tests/ci_visibility/suitespec.yml delete mode 100644 tests/contrib/jobspec.yml create mode 100644 tests/contrib/suitespec.yml delete mode 100644 tests/debugging/jobspec.yml create mode 100644 tests/debugging/suitespec.yml delete mode 100644 tests/jobspec.yml delete mode 100644 tests/llmobs/jobspec.yml create mode 100644 tests/llmobs/suitespec.yml create mode 100644 tests/suitespec.yml diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index db016870e75..d8347ed8643 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -281,109 +281,6 @@ commands: name: Get APM Test Agent Trace Check Results command: bash ./scripts/get-test-agent-results.sh - run_hatch_env_test: - description: "Run hatch env test" - parameters: - env: - type: string - default: "" - snapshot: - type: boolean - default: false - docker_services: - type: string - default: "" - store_coverage: - type: boolean - default: true - trace_agent_url: - type: string - default: "http://localhost:9126" - run_agent_checks: - type: boolean - default: true - steps: - - checkout - - attach_workspace: - at: . - - restore_cache: - keys: - - lastsuccess-{{ .Environment.CIRCLE_BRANCH }}-<>-{{ .Environment.CIRCLE_NODE_INDEX }} - - setup_hatch - - when: - condition: - << parameters.snapshot >> - steps: - - start_docker_services: - env: SNAPSHOT_CI=1 - services: testagent << parameters.docker_services >> - - run: - name: Run tests - environment: - DD_TRACE_AGENT_URL: << parameters.trace_agent_url >> - command: | - ulimit -c unlimited - ./scripts/run-test-suite-hatch '<>' 1 - - run: - command: | - mkdir -p /tmp/core_dumps - cp core.* /tmp/core_dumps || true - ./scripts/bt - when: on_fail - - store_artifacts: - name: "Store core dumps" - path: /tmp/core_dumps - - unless: - condition: - << parameters.snapshot >> - steps: - - run: - name: Run tests - command: | - hatch env show --json | jq -r 'keys[] | select(. | contains("<< parameters.env >>"))' | sort | circleci tests split | xargs -n 1 -I {} hatch run {}:test - - when: - condition: - << parameters.store_coverage >> - steps: - - save_coverage - - store_test_results: - path: test-results - - store_artifacts: - path: test-results - - save_cache: - key: lastsuccess-{{ .Environment.CIRCLE_BRANCH }}-<>-{{ .Environment.CIRCLE_NODE_INDEX }}-{{ epoch }} - paths: - - ./latest-success-commit - - run: - name: "Store Test Agent Supported Integrations Data" - command: | - if [[ -z "$(curl -s << parameters.trace_agent_url >>/test/integrations/tested_versions)" ]]; then - # No integrations were tested. Not saving any artifacts - echo "Response body is empty. Skipping saving integration artifacts." - else - # make temporary files to save response data to - response=$(mktemp) && headers=$(mktemp) - # create artifacts directory if it doesn't exist - [ -d "./artifacts" ] || mkdir -p "./artifacts" - # get tested integrations - curl -o "$response" -D "$headers" << parameters.trace_agent_url >>/test/integrations/tested_versions - # get filename representing the name of the tested integration from headers - filename=$(awk -F': ' '/file-name/{print $2}' "$headers" | tr -d '\r\n') - # copy data to final file and remove temp files - mv "$response" "artifacts/${filename}_supported_versions.csv" - rm "$headers" - fi - - store_artifacts: - path: artifacts - destination: supported-integrations - - when: - condition: - << parameters.run_agent_checks >> - steps: - - run: - name: Get APM Test Agent Trace Check Results - command: bash ./scripts/get-test-agent-results.sh - executors: cimg_base: docker: diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a32aa169c7a..771e1a147ae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,7 +39,7 @@ ddtrace/internal/compat.py @DataDog/python-guild @DataDog/apm-core-pyt ddtrace/settings/config.py @DataDog/python-guild @DataDog/apm-sdk-api-python docs/ @DataDog/python-guild tests/utils.py @DataDog/python-guild -tests/.suitespec.json @DataDog/python-guild @DataDog/apm-core-python +tests/suitespec.yml @DataDog/python-guild @DataDog/apm-core-python tests/suitespec.py @DataDog/python-guild @DataDog/apm-core-python # Core / Language Platform diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 030082260e7..01c36591e10 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -119,18 +119,5 @@ build_base_venvs: - export DD_TRACE_AGENT_URL="http://testagent:9126" - ln -s "${CI_PROJECT_DIR}" "/root/project" -slotscheck: - extends: .testrunner - stage: tests - needs: [] - script: - - hatch run slotscheck:_ - -conftests: - extends: .testrunner - stage: tests - needs: [] - script: - - hatch run meta-testing:meta-testing # Required jobs will appear here diff --git a/docs/contributing-integrations.rst b/docs/contributing-integrations.rst index e7a2899b175..9d7d2d202ee 100644 --- a/docs/contributing-integrations.rst +++ b/docs/contributing-integrations.rst @@ -216,7 +216,5 @@ The following is the check list for ensuring you have all of the components to h - The virtual environment configurations for your tests in ``riotfile.py``. - The Circle CI configurations for your tests in ``.circleci/config.templ.yml``. - Your integration added to ``PATCH_MODULES`` in ``ddtrace/_monkey.py`` to enable auto instrumentation for it. -- The relevant file paths for your integration added to ``tests/.suitespec.json`` in two locations: - - Add non-test file paths under ``components``. - - Add test file paths under ``suites``. +- The relevant file paths for your integration added to a suitespec file (see ``tests/README.md`` for details). - A release note for your addition generated with ``riot run reno new YOUR_TITLE_SLUG``, which will add ``releasenotes/notes/YOUR_TITLE_SLUG.yml``. diff --git a/docs/contributing-testing.rst b/docs/contributing-testing.rst index e4c422a37de..c096e0407ba 100644 --- a/docs/contributing-testing.rst +++ b/docs/contributing-testing.rst @@ -153,24 +153,8 @@ Next, we will need to add a new CircleCI job to run the newly added test suite a pattern: 'asyncio' -After this, a new component must be added to ``tests/.suitespec.json`` under ``"components":`` like: - -.. code-block:: JSON - - "asyncio": [ - "ddtrace/contrib/asyncio/*" - ], - -Lastly, we will register it as a suite in the same file under ``"suites":``: - -.. code-block:: JSON - - "asyncio": [ - "@asyncio", - "tests/contrib/asyncio/*" - ], - -Once you've completed these steps, CircleCI will run the new test suite. +After this, check out ``tests/README.md`` for instructions on how to add new +jobs to run the new tests in CI. How do I update a Riot environment to use the latest version of a package? -------------------------------------------------------------------------- diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index ed29d8fd07d..d3c185a9360 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -298,4 +298,5 @@ assertIn # tests/contrib/openai/test_openai_v1.py Nam # docs/configuration.rst -uest \ No newline at end of file +uest +suitespec diff --git a/hatch.toml b/hatch.toml index dddc78a0f8b..959551a7dbe 100644 --- a/hatch.toml +++ b/hatch.toml @@ -21,6 +21,7 @@ dependencies = [ "ruff==0.1.3", "clang-format==18.1.5", "cmake-format==0.6.13", + "ruamel.yaml==0.18.6", ] [envs.lint.scripts] @@ -133,6 +134,7 @@ detached = true python = "3.10" extra-dependencies = [ "packaging==23.1", + "ruamel.yaml==0.18.6", ] [envs.scripts.scripts] diff --git a/scripts/gen_circleci_config.py b/scripts/gen_circleci_config.py index 4afbec47cc1..0c7c8344e58 100644 --- a/scripts/gen_circleci_config.py +++ b/scripts/gen_circleci_config.py @@ -11,15 +11,15 @@ def gen_required_suites(template: dict) -> None: """Generate the list of test suites that need to be run.""" from needs_testrun import extract_git_commit_selections - from needs_testrun import for_each_testrun_needed as fetn + from needs_testrun import for_each_testrun_needed from suitespec import get_suites - suites = get_suites() - jobs = set(template["jobs"].keys()) - required_suites = template["requires_tests"]["requires"] = [] - fetn( - suites=sorted(suites & jobs), + for_each_testrun_needed( + suites=sorted( + set(n.rpartition("::")[-1] for n, s in get_suites().items() if not s.get("skip", False)) + & set(template["jobs"].keys()) + ), action=lambda suite: required_suites.append(suite), git_selections=extract_git_commit_selections(os.getenv("GIT_COMMIT_DESC", "")), ) @@ -77,12 +77,7 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: check( name="Run scripts/*.py tests", command="hatch run scripts:test", - paths={"docker*", "scripts/*.py", "scripts/mkwheelhouse", "scripts/run-test-suite", "tests/.suitespec.json"}, - ) - check( - name="Validate suitespec JSON file", - command="python -m tests.suitespec", - paths={"docker*", "tests/.suitespec.json", "tests/suitespec.py"}, + paths={"docker*", "scripts/*.py", "scripts/mkwheelhouse", "scripts/run-test-suite", "**suitespec.yml"}, ) check( name="Check suitespec coverage", diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 2c7e5a5b6c2..2b139ce798d 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -13,17 +13,20 @@ class JobSpec: name: str runner: str - is_snapshot: bool = False + pattern: t.Optional[str] = None + snapshot: bool = False services: t.Optional[t.Set[str]] = None env: t.Optional[t.Dict[str, str]] = None parallelism: t.Optional[int] = None retry: t.Optional[int] = None timeout: t.Optional[int] = None + skip: bool = False + paths: t.Optional[t.Set[str]] = None # ignored def __str__(self) -> str: lines = [] base = f".test_base_{self.runner}" - if self.is_snapshot: + if self.snapshot: base += "_snapshot" lines.append(f"{self.name}:") @@ -33,16 +36,20 @@ def __str__(self) -> str: lines.append(" services:") _services = [f"!reference [.services, {_}]" for _ in self.services] - if self.is_snapshot: + if self.snapshot: _services.insert(0, f"!reference [{base}, services]") for service in _services: lines.append(f" - {service}") - if self.env: - lines.append(" variables:") - for key, value in self.env.items(): - lines.append(f" {key}: {value}") + env = self.env + if not env or "SUITE_NAME" not in env: + env = env or {} + env["SUITE_NAME"] = self.pattern or self.name + + lines.append(" variables:") + for key, value in env.items(): + lines.append(f" {key}: {value}") if self.parallelism is not None: lines.append(f" parallel: {self.parallelism}") @@ -56,57 +63,107 @@ def __str__(self) -> str: return "\n".join(lines) -def collect_jobspecs() -> dict: - # Recursively search for jobspec.yml in TESTS - jobspecs = {} - for js in TESTS.rglob("jobspec.yml"): - with YAML() as yaml: - LOGGER.info("Loading jobspecs from %s", js) - jobspecs.update(yaml.load(js)) - - return jobspecs - - def gen_required_suites() -> None: """Generate the list of test suites that need to be run.""" from needs_testrun import extract_git_commit_selections - from needs_testrun import for_each_testrun_needed as fetn - from suitespec import get_suites - - jobspecs = collect_jobspecs() - - suites = get_suites() - required_suites = [] + from needs_testrun import for_each_testrun_needed + import suitespec - for suite in suites: - if suite not in jobspecs: - print(f"WARNING: Suite {suite} has no jobspec", file=sys.stderr) - continue + suites = suitespec.get_suites() - for job in jobspecs: - if job not in suites: - print(f"WARNING: Job {job} has no suitespec", file=sys.stderr) - continue + required_suites: t.List[str] = [] - fetn( - suites=sorted(suites), + for_each_testrun_needed( + suites=sorted(suites.keys()), action=lambda suite: required_suites.append(suite), git_selections=extract_git_commit_selections(os.getenv("CI_COMMIT_MESSAGE", "")), ) + # Exclude the suites that are run in CircleCI. These likely don't run in + # GitLab yet. + with YAML() as yaml: + circleci_config = yaml.load(ROOT / ".circleci" / "config.templ.yml") + circleci_jobs = set(circleci_config["jobs"].keys()) + # Copy the template file - (GITLAB / "tests-gen.yml").write_text( + TESTS_GEN.write_text( (GITLAB / "tests.yml").read_text().replace(r"{{services.yml}}", (GITLAB / "services.yml").read_text()) ) # Generate the list of suites to run - with (GITLAB / "tests-gen.yml").open("a") as f: + with TESTS_GEN.open("a") as f: for suite in required_suites: - if suite not in jobspecs: - print(f"Suite {suite} not found in jobspec", file=sys.stderr) + if suite.rsplit("::", maxsplit=1)[-1] in circleci_jobs: + LOGGER.debug("Skipping CircleCI suite %s", suite) + continue + + jobspec = JobSpec(suite, **suites[suite]) + if jobspec.skip: + LOGGER.debug("Skipping suite %s", suite) continue - print(str(JobSpec(suite, **jobspecs[suite])), file=f) + print(str(jobspec), file=f) + + +def gen_pre_checks() -> None: + """Generate the list of pre-checks that need to be run.""" + from needs_testrun import pr_matches_patterns + + def check(name: str, command: str, paths: t.Set[str]) -> None: + if pr_matches_patterns(paths): + with TESTS_GEN.open("a") as f: + print(f'"{name}":', file=f) + print(" extends: .testrunner", file=f) + print(" stage: tests", file=f) + print(" needs: []", file=f) + print(" script:", file=f) + print(f" - {command}", file=f) + + check( + name="Style", + command="hatch run lint:style", + paths={"docker*", "*.py", "*.pyi", "hatch.toml", "pyproject.toml", "*.cpp", "*.h"}, + ) + check( + name="Typing", + command="hatch run lint:typing", + paths={"docker*", "*.py", "*.pyi", "hatch.toml", "mypy.ini"}, + ) + check( + name="Security", + command="hatch run lint:security", + paths={"docker*", "ddtrace/*", "hatch.toml"}, + ) + check( + name="Run riotfile.py tests", + command="hatch run lint:riot", + paths={"docker*", "riotfile.py", "hatch.toml"}, + ) + check( + name="Style: Test snapshots", + command="hatch run lint:fmt-snapshots && git diff --exit-code tests/snapshots hatch.toml", + paths={"docker*", "tests/snapshots/*", "hatch.toml"}, + ) + check( + name="Run scripts/*.py tests", + command="hatch run scripts:test", + paths={"docker*", "scripts/*.py", "scripts/mkwheelhouse", "scripts/run-test-suite", "**suitespec.yml"}, + ) + check( + name="Check suitespec coverage", + command="hatch run lint:suitespec-check", + paths={"*"}, + ) + check( + name="conftest", + command="hatch run meta-testing:meta-testing", + paths={"**conftest.py"}, + ) + check( + name="slotscheck", + command="hatch run slotscheck:_", + paths={"**.py"}, + ) # ----------------------------------------------------------------------------- @@ -128,17 +185,23 @@ def gen_required_suites() -> None: argp = ArgumentParser() argp.add_argument("--verbose", "-v", action="store_true", help="Verbose output") +argp.add_argument("--debug", "-d", action="store_true", help="Debug output") args = argp.parse_args() -if args.verbose: +if args.debug: + LOGGER.setLevel(logging.DEBUG) +elif args.verbose: LOGGER.setLevel(logging.INFO) ROOT = Path(__file__).parents[1] GITLAB = ROOT / ".gitlab" TESTS = ROOT / "tests" +TESTS_GEN = GITLAB / "tests-gen.yml" # Make the scripts and tests folders available for importing. sys.path.append(str(ROOT / "scripts")) sys.path.append(str(ROOT / "tests")) +has_error = False + LOGGER.info("Configuration generation steps:") for name, func in dict(globals()).items(): if name.startswith("gen_"): @@ -146,8 +209,9 @@ def gen_required_suites() -> None: try: start = time() func() - end = time() - LOGGER.info("- %s: %s [took %dms]", name, desc, int((end - start) / 1e6)) + LOGGER.info("- %s: %s [took %dms]", name, desc, int((time() - start) / 1e6)) except Exception as e: LOGGER.error("- %s: %s [reason: %s]", name, desc, str(e), exc_info=True) has_error = True + +sys.exit(has_error) diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index ecac780e328..99ebba2c18c 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -189,6 +189,7 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo return bool(matches) +@cache def _get_pr_number() -> int: # CircleCI number = os.environ.get("CIRCLE_PR_NUMBER") diff --git a/tests/.suitespec.json b/tests/.suitespec.json deleted file mode 100644 index 2b58ced3344..00000000000 --- a/tests/.suitespec.json +++ /dev/null @@ -1,1529 +0,0 @@ -{ - "components": { - "$setup": [ - "setup.py", - "setup.cfg", - "pyproject.toml" - ], - "$harness": [ - "docker/*", - "docker-compose.yml", - "riotfile.py", - ".riot/requirements/*", - "scripts/ddtest", - "scripts/run-test-suite", - "hatch.toml", - "tests/conftest.py", - "tests/utils.py", - "tests/__init__.py", - "tests/.suitespec.json", - "tests/suitespec.py", - ".circleci/*", - "tests/meta/*", - "tests/smoke_test.py", - "tests/subprocesstest.py", - "tests/wait-for-services.py", - "tests/webclient.py", - "tests/test_module/*", - ".gitlab-ci.yml", - ".gitlab/*", - "tests/jobspec.yml" - ], - "core": [ - "ddtrace/internal/__init__.py", - "ddtrace/internal/_exceptions.py", - "ddtrace/internal/_file_queue.py", - "ddtrace/internal/_rand.pyi", - "ddtrace/internal/_rand.pyx", - "ddtrace/internal/_stdint.h", - "ddtrace/internal/_threads.*", - "ddtrace/internal/_unpatched.py", - "ddtrace/internal/agent.py", - "ddtrace/internal/assembly.py", - "ddtrace/internal/atexit.py", - "ddtrace/internal/compat.py", - "ddtrace/internal/core/*", - "ddtrace/internal/datadog/__init__.py", - "ddtrace/internal/debug.py", - "ddtrace/internal/dogstatsd.py", - "ddtrace/internal/forksafe.py", - "ddtrace/internal/gitmetadata.py", - "ddtrace/internal/glob_matching.py", - "ddtrace/internal/logger.py", - "ddtrace/internal/hostname.py", - "ddtrace/internal/http.py", - "ddtrace/internal/injection.py", - "ddtrace/internal/logger.py", - "ddtrace/internal/metrics.py", - "ddtrace/internal/module.py", - "ddtrace/internal/packages.py", - "ddtrace/internal/third-party.tar.gz", - "ddtrace/internal/periodic.py", - "ddtrace/internal/rate_limiter.py", - "ddtrace/internal/safety.py", - "ddtrace/internal/service.py", - "ddtrace/internal/uds.py", - "ddtrace/internal/utils/*", - "ddtrace/internal/uwsgi.py", - "ddtrace/internal/wrapping/*", - "ddtrace/__init__.py", - "ddtrace/py.typed", - "ddtrace/version.py", - "ddtrace/settings/config.py", - "src/core/*" - ], - "bootstrap": [ - "ddtrace/bootstrap/*", - "ddtrace/commands/*", - "ddtrace/auto.py" - ], - "tracing": [ - "ddtrace/_hooks.py", - "ddtrace/_logger.py", - "ddtrace/_monkey.py", - "ddtrace/_trace/*", - "ddtrace/trace/*", - "ddtrace/constants.py", - "ddtrace/context.py", - "ddtrace/filters.py", - "ddtrace/pin.py", - "ddtrace/provider.py", - "ddtrace/sampler.py", - "ddtrace/sampling_rule.py", - "ddtrace/span.py", - "ddtrace/tracer.py", - "ddtrace/tracing/*", - "ddtrace/settings/__init__.py", - "ddtrace/settings/config.py", - "ddtrace/settings/http.py", - "ddtrace/settings/exceptions.py", - "ddtrace/settings/integration.py", - "ddtrace/internal/_encoding.py*", - "ddtrace/internal/_tagset.py*", - "ddtrace/internal/_utils.*", - "ddtrace/internal/constants.py", - "ddtrace/internal/encoding.py", - "ddtrace/internal/flare/*", - "ddtrace/internal/pack.h", - "ddtrace/internal/pack_template.h", - "ddtrace/internal/peer_service/*", - "ddtrace/settings/peer_service.py", - "ddtrace/internal/processor/__init__.py", - "ddtrace/internal/processor/stats.py", - "ddtrace/internal/runtime/*", - "ddtrace/internal/sampling.py", - "ddtrace/internal/schema/*", - "ddtrace/internal/sma.py", - "ddtrace/internal/tracemethods.py", - "ddtrace/internal/sysdep.h", - "ddtrace/internal/writer/*" - ], - "runtime": [ - "ddtrace/runtime/*" - ], - "contrib": [ - "ddtrace/contrib/__init__.py", - "ddtrace/contrib/trace_utils.py", - "ddtrace/contrib/trace_utils_async.py", - "ddtrace/contrib/internal/*", - "ddtrace/ext/__init__.py", - "ddtrace/ext/http.py", - "ddtrace/ext/net.py", - "ddtrace/ext/schema.py", - "ddtrace/ext/sql.py", - "ddtrace/ext/test.py", - "ddtrace/ext/user.py", - "ddtrace/propagation/*", - "ddtrace/settings/_database_monitoring.py", - "tests/contrib/patch.py", - "tests/contrib/config.py", - "tests/contrib/__init__.py", - "tests/contrib/jobspec.yml" - ], - "redis": [ - "ddtrace/contrib/rediscluster/*", - "ddtrace/contrib/internal/rediscluster/*", - "ddtrace/contrib/redis/*", - "ddtrace/contrib/internal/redis/*", - "ddtrace/contrib/aredis/*", - "ddtrace/contrib/internal/aredis/*", - "ddtrace/contrib/yaaredis/*", - "ddtrace/contrib/internal/yaaredis/*", - "ddtrace/_trace/utils_redis.py", - "ddtrace/contrib/redis_utils.py", - "ddtrace/contrib/trace_utils_redis.py", - "ddtrace/ext/redis.py" - ], - "avro": [ - "ddtrace/contrib/avro/*", - "ddtrace/contrib/internal/avro/*" - ], - "protobuf": [ - "ddtrace/contrib/protobuf/*", - "ddtrace/contrib/internal/protobuf/*" - ], - "mongo": [ - "ddtrace/contrib/pymongo/*", - "ddtrace/contrib/internal/pymongo/*", - "ddtrace/contrib/mongoengine/*", - "ddtrace/contrib/internal/mongoengine/*", - "ddtrace/ext/mongo.py" - ], - "pg": [ - "ddtrace/contrib/aiopg/*", - "ddtrace/contrib/internal/aiopg/*", - "ddtrace/contrib/asyncpg/*", - "ddtrace/contrib/internal/psycopg/*", - "ddtrace/contrib/internal/asyncpg/*", - "ddtrace/contrib/psycopg/*" - ], - "datastreams": [ - "ddtrace/internal/datastreams/*", - "ddtrace/data_streams.py", - "ddtrace/ext/schema.py" - ], - "debugging": [ - "ddtrace/debugging/*", - "ddtrace/settings/dynamic_instrumentation.py", - "ddtrace/settings/exception_replay.py" - ], - "ci": [ - "ddtrace/ext/ci.py" - ], - "ci_visibility": [ - "ddtrace/internal/ci_visibility/*", - "ddtrace/ext/test_visibility/*", - "ddtrace/ext/test.py" - ], - "coverage": [ - "ddtrace/contrib/coverage/*", - "ddtrace/contrib/internal/coverage/*" - ], - "dd_coverage": [ - "ddtrace/internal/coverage/*" - ], - "llmobs": [ - "ddtrace/llmobs/*" - ], - "settings": [ - "ddtrace/settings/*" - ], - "sourcecode": [ - "ddtrace/sourcecode/*" - ], - "telemetry": [ - "ddtrace/internal/telemetry/*" - ], - "opentracer": [ - "ddtrace/opentracer/*" - ], - "tornado": [ - "ddtrace/contrib/tornado/*", - "ddtrace/contrib/internal/tornado/*" - ], - "pynamodb": [ - "ddtrace/contrib/pynamodb/*", - "ddtrace/contrib/internal/pynamodb/*" - ], - "pyodbc": [ - "ddtrace/contrib/pyodbc/*", - "ddtrace/contrib/internal/pyodbc/*" - ], - "flask": [ - "ddtrace/contrib/flask/*", - "ddtrace/contrib/internal/flask/*", - "ddtrace/contrib/flask_cache/*", - "ddtrace/contrib/internal/flask_cache/*", - "ddtrace/contrib/flask_login/*" - ], - "fastapi": [ - "ddtrace/contrib/fastapi/*", - "ddtrace/contrib/internal/fastapi/*" - ], - "gevent": [ - "ddtrace/contrib/gevent/*", - "ddtrace/contrib/internal/gevent/*" - ], - "git": [ - "ddtrace/ext/git.py" - ], - "starlette": [ - "ddtrace/contrib/starlette/*", - "ddtrace/contrib/internal/starlette/*" - ], - "structlog": [ - "ddtrace/contrib/structlog/*", - "ddtrace/contrib/internal/structlog/*" - ], - "asgi": [ - "ddtrace/contrib/asgi/*", - "ddtrace/contrib/internal/asgi/*" - ], - "pymemcache": [ - "ddtrace/contrib/pymemcache/*", - "ddtrace/contrib/internal/pymemcache/*", - "ddtrace/ext/memcached.py" - ], - "snowflake": [ - "ddtrace/contrib/snowflake/*", - "ddtrace/contrib/internal/snowflake/*" - ], - "sanic": [ - "ddtrace/contrib/sanic/*", - "ddtrace/contrib/internal/sanic/*" - ], - "requests": [ - "ddtrace/contrib/requests/*", - "ddtrace/contrib/internal/requests/*" - ], - "pyramid": [ - "ddtrace/contrib/pyramid/*", - "ddtrace/contrib/internal/pyramid/*" - ], - "langchain": [ - "ddtrace/contrib/langchain/*", - "ddtrace/contrib/internal/langchain/*" - ], - "anthropic": [ - "ddtrace/contrib/anthropic/*", - "ddtrace/contrib/internal/anthropic/*" - ], - "google_generativeai": [ - "ddtrace/contrib/google_generativeai/*", - "ddtrace/contrib/internal/google_generativeai/*" - ], - "vertexai": [ - "ddtrace/contrib/vertexai/*", - "ddtrace/contrib/internal/vertexai/*" - ], - "subprocess": [ - "ddtrace/contrib/subprocess/*", - "ddtrace/contrib/internal/subprocess/*" - ], - "sqlalchemy": [ - "ddtrace/contrib/sqlalchemy/*", - "ddtrace/contrib/internal/sqlalchemy/*" - ], - "opentelemetry": [ - "ddtrace/settings/_otel_remapper.py", - "ddtrace/opentelemetry/*", - "ddtrace/internal/opentelemetry/*" - ], - "profiling": [ - "ddtrace/profiling/*", - "ddtrace/internal/datadog/profiling/*", - "ddtrace/internal/processor/endpoint_call_counter.py", - "ddtrace/settings/profiling.py" - ], - "vendor": [ - "ddtrace/vendor/*" - ], - "urllib3": [ - "ddtrace/contrib/urllib3/*", - "ddtrace/contrib/internal/urllib3/*" - ], - "webbrowser": [ - "ddtrace/contrib/webbrowser/*", - "ddtrace/contrib/internal/webbrowser/*" - ], - "urllib": [ - "ddtrace/contrib/urllib/*", - "ddtrace/contrib/internal/urllib/*" - ], - "rq": [ - "ddtrace/contrib/rq/*" - ], - "mako": [ - "ddtrace/contrib/mako/*", - "ddtrace/contrib/internal/mako/*" - ], - "jinja2": [ - "ddtrace/contrib/jinja2/*", - "ddtrace/contrib/internal/jinja2/*" - ], - "kombu": [ - "ddtrace/contrib/kombu/*", - "ddtrace/contrib/internal/kombu/*", - "ddtrace/ext/kombu.py" - ], - "wsgi": [ - "ddtrace/contrib/wsgi/*", - "ddtrace/contrib/internal/wsgi/*" - ], - "vertica": [ - "ddtrace/contrib/vertica/*", - "ddtrace/contrib/internal/vertica/*" - ], - "algoliasearch": [ - "ddtrace/contrib/algoliasearch/*", - "ddtrace/contrib/internal/algoliasearch/*" - ], - "kafka": [ - "ddtrace/contrib/kafka/*", - "ddtrace/contrib/internal/kafka/*", - "ddtrace/ext/kafka.py" - ], - "graphql": [ - "ddtrace/contrib/graphql/*", - "ddtrace/contrib/internal/graphql/*" - ], - "grpc": [ - "ddtrace/contrib/grpc/*", - "ddtrace/contrib/internal/grpc/*" - ], - "gunicorn": [ - "ddtrace/contrib/gunicorn/*" - ], - "httplib": [ - "ddtrace/contrib/httplib/*", - "ddtrace/contrib/internal/httplib/*" - ], - "httpx": [ - "ddtrace/contrib/httpx/*", - "ddtrace/contrib/internal/httpx/*" - ], - "mariadb": [ - "ddtrace/contrib/mariadb/*", - "ddtrace/contrib/internal/mariadb/*" - ], - "molten": [ - "ddtrace/contrib/molten/*", - "ddtrace/contrib/internal/molten/*" - ], - "botocore": [ - "ddtrace/contrib/botocore/*", - "ddtrace/contrib/internal/botocore/*", - "ddtrace/contrib/boto/*", - "ddtrace/contrib/internal/boto/*", - "ddtrace/contrib/aiobotocore/*", - "ddtrace/contrib/internal/aiobotocore/*" - ], - "mysql": [ - "ddtrace/contrib/mysql/*", - "ddtrace/contrib/internal/mysql/*", - "ddtrace/contrib/mysqldb/*", - "ddtrace/contrib/internal/mysqldb/*", - "ddtrace/contrib/pymysql/*", - "ddtrace/contrib/internal/pymysql/*", - "ddtrace/contrib/aiomysql/*", - "ddtrace/contrib/internal/aiomysql/*" - ], - "pylibmc": [ - "ddtrace/contrib/pylibmc/*", - "ddtrace/contrib/internal/pylibmc/*" - ], - "logbook": [ - "ddtrace/contrib/logbook/*", - "ddtrace/contrib/internal/logbook/*" - ], - "logging": [ - "ddtrace/contrib/logging/*", - "ddtrace/contrib/internal/logging/*" - ], - "loguru": [ - "ddtrace/contrib/logging/*", - "ddtrace/contrib/internal/logging/*", - "ddtrace/contrib/loguru/*", - "ddtrace/contrib/internal/loguru/*" - ], - "sqlite3": [ - "ddtrace/contrib/sqlite3/*", - "ddtrace/contrib/internal/sqlite3/*" - ], - "asyncio": [ - "ddtrace/contrib/asyncio/*" - ], - "futures": [ - "ddtrace/contrib/futures/*", - "ddtrace/contrib/internal/futures/*" - ], - "dbapi": [ - "ddtrace/contrib/dbapi/*", - "ddtrace/contrib/dbapi_async/*", - "ddtrace/ext/db.py" - ], - "aws_lambda": [ - "ddtrace/contrib/aws_lambda/*", - "ddtrace/contrib/internal/aws_lambda/*", - "ddtrace/ext/aws.py" - ], - "aiohttp": [ - "ddtrace/contrib/aiohttp/*", - "ddtrace/contrib/internal/aiohttp/*", - "ddtrace/contrib/aiohttp_jinja2/*", - "ddtrace/contrib/internal/aiohttp_jinja2/*" - ], - "openai": [ - "ddtrace/contrib/openai/*", - "ddtrace/contrib/internal/openai/*" - ], - "falcon": [ - "ddtrace/contrib/falcon/*", - "ddtrace/contrib/internal/falcon/*" - ], - "elasticsearch": [ - "ddtrace/contrib/elasticsearch/*", - "ddtrace/contrib/internal/elasticsearch/*", - "ddtrace/ext/elasticsearch.py" - ], - "dogpile_cache": [ - "ddtrace/contrib/dogpile_cache/*", - "ddtrace/contrib/internal/dogpile_cache/*" - ], - "cherrypy": [ - "ddtrace/contrib/cherrypy/*", - "ddtrace/contrib/internal/cherrypy/*" - ], - "celery": [ - "ddtrace/contrib/celery/*", - "ddtrace/contrib/internal/celery/*" - ], - "dramatiq": [ - "ddtrace/contrib/dramatiq/*", - "ddtrace/contrib/internal/dramatiq/*" - ], - "cassandra": [ - "ddtrace/contrib/cassandra/*", - "ddtrace/contrib/internal/cassandra/*", - "ddtrace/ext/cassandra.py" - ], - "bottle": [ - "ddtrace/contrib/bottle/*", - "ddtrace/contrib/internal/bottle/*" - ], - "consul": [ - "ddtrace/contrib/consul/*", - "ddtrace/contrib/internal/consul/*", - "ddtrace/ext/consul.py" - ], - "django": [ - "ddtrace/contrib/django/*", - "ddtrace/contrib/internal/django/*" - ], - "aiopg": [ - "ddtrace/contrib/aiopg/*", - "ddtrace/contrib/internal/aiopg/*" - ], - "pytest": [ - "ddtrace/contrib/pytest/*", - "ddtrace/contrib/pytest_bdd/*", - "ddtrace/contrib/pytest_benchmark/*" - ], - "unittest": [ - "ddtrace/contrib/unittest/*" - ], - "appsec": [ - "ddtrace/appsec/*", - "ddtrace/settings/asm.py" - ], - "appsec_iast": [ - "ddtrace/appsec/iast/*" - ], - "symbol_db": [ - "ddtrace/internal/symbol_db/*", - "ddtrace/settings/symbol_db.py" - ], - "serverless": [ - "ddtrace/internal/serverless/*" - ], - "remoteconfig": [ - "ddtrace/internal/remoteconfig/*" - ], - "codeowners": [ - "ddtrace/internal/codeowners.py" - ] - }, - "suites": { - "internal": [ - "@core", - "@remoteconfig", - "@symbol_db", - "@tracing", - "ddtrace/internal/*", - "tests/internal/*", - "tests/submod/*", - "tests/cache/*" - ], - "ddtracerun": [ - "@contrib", - "@bootstrap", - "@core", - "tests/commands/*", - "tests/ddtrace_run.py" - ], - "debugger": [ - "@debugging", - "@bootstrap", - "@core", - "@remoteconfig", - "@tracing", - "tests/debugging/*" - ], - "kafka": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@kafka", - "@datastreams", - "tests/contrib/kafka/*", - "tests/snapshots/tests.contrib.kafka.*" - ], - "tracer": [ - "@tracing", - "@bootstrap", - "@core", - "@contrib", - "@llmobs", - "@serverless", - "@remoteconfig", - "@futures", - "@ci_visibility", - "tests/tracer/*", - "tests/snapshots/test_*" - ], - "integration_agent": [ - "@tracing", - "@bootstrap", - "@core", - "@contrib", - "tests/integration/*", - "tests/snapshots/tests.integration.*" - ], - "integration_testagent": [ - "@tracing", - "@bootstrap", - "@core", - "@contrib", - "tests/integration/*", - "tests/snapshots/tests.integration.*" - ], - "stdlib": [ - "@tracing", - "@bootstrap", - "@core", - "@logging", - "@asyncio", - "@futures", - "@sqlite3", - "@dbapi", - "@contrib", - "tests/contrib/logging/*", - "tests/contrib/sqlite3/*", - "tests/contrib/asyncio/*", - "tests/contrib/futures/*", - "tests/contrib/dbapi/*", - "tests/contrib/dbapi_async/*" - ], - "appsec": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig", - "tests/appsec/*" - ], - "appsec_iast": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig", - "tests/appsec/iast/*" - ], - "appsec_iast_native": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig" - ], - "appsec_iast_memcheck": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig", - "tests/appsec/iast/*", - "tests/appsec/iast_memcheck/*" - ], - "appsec_iast_packages": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig", - "tests/appsec/iast/*", - "tests/appsec/iast_packages/*" - ], - "appsec_integrations": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@remoteconfig", - "tests/appsec/*", - "tests/snapshots/tests.appsec.*" - ], - "appsec_threats_django": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@asgi", - "@wsgi", - "@django", - "@remoteconfig", - "tests/appsec/*", - "tests/appsec/contrib_appsec/*" - ], - "appsec_threats_flask": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@asgi", - "@wsgi", - "@flask", - "@remoteconfig", - "tests/appsec/*", - "tests/appsec/contrib_appsec/*" - ], - "appsec_threats_fastapi": [ - "@bootstrap", - "@core", - "@tracing", - "@appsec", - "@appsec_iast", - "@asgi", - "@wsgi", - "@fastapi", - "@starlette", - "@remoteconfig", - "tests/appsec/*", - "tests/appsec/contrib_appsec/*" - ], - "aws_lambda": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@aws_lambda", - "tests/contrib/aws_lambda/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "datastreams": [ - "@bootstrap", - "@core", - "@tracing", - "@datastreams", - "tests/datastreams/*" - ], - "ci_visibility": [ - "@ci_visibility", - "@ci", - "@coverage", - "@git", - "@pytest", - "@codeowners", - "tests/ci_visibility/*" - ], - "dd_coverage": [ - "@bootstrap", - "@core", - "@tracing", - "@dd_coverage", - "tests/coverage/*" - ], - "llmobs": [ - "@bootstrap", - "@core", - "@tracing", - "@llmobs", - "tests/llmobs/*" - ], - "sourcecode": [ - "@bootstrap", - "@core", - "@contrib", - "@sourcecode", - "tests/sourcecode/*" - ], - "telemetry": [ - "@bootstrap", - "@contrib", - "@core", - "@telemetry", - "@tracing", - "@settings", - "@profiling", - "tests/telemetry/*", - "tests/snapshots/tests.telemetry.*" - ], - "openai": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@openai", - "@requests", - "@llmobs", - "tests/contrib/openai/*", - "tests/snapshots/tests.contrib.openai.*" - ], - "opentracer": [ - "@bootstrap", - "@core", - "@tracing", - "@opentracer", - "tests/opentracer/*" - ], - "opentelemetry": [ - "@bootstrap", - "@core", - "@tracing", - "@opentelemetry", - "tests/opentelemetry/*", - "tests/snapshots/tests.opentelemetry.*" - ], - "profile": [ - "@bootstrap", - "@core", - "@profiling", - "tests/profiling/*", - "tests/profiling_v2/*" - ], - "vendor": [ - "@vendor", - "tests/vendor/*" - ], - "botocore": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@llmobs", - "@botocore", - "@llmobs", - "@datastreams", - "tests/contrib/botocore/*", - "tests/contrib/boto/*", - "tests/snapshots/tests.contrib.botocore*" - ], - "test_logging": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@logging", - "tests/contrib/logging/*" - ], - "logbook": [ - "@core", - "@contrib", - "@tracing", - "@logbook", - "tests/contrib/logbook/*" - ], - "loguru": [ - "@core", - "@contrib", - "@tracing", - "@loguru", - "tests/contrib/loguru/*" - ], - "asyncpg": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pg", - "tests/contrib/asyncpg/*", - "tests/snapshots/tests.contrib.{suite}.*", - "tests/contrib/shared_tests_async.py" - ], - "aiohttp": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@aiohttp", - "tests/contrib/aiohttp/*", - "tests/contrib/aiohttp_jinja2/*", - "tests/snapshots/tests.contrib.aiohttp_jinja2.*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "asgi": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@asyncio", - "@appsec", - "@asgi", - "tests/contrib/asgi/*" - ], - "bottle": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@bottle", - "tests/contrib/bottle/*" - ], - "cassandra": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@cassandra", - "tests/contrib/cassandra/*" - ], - "celery": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@celery", - "tests/contrib/celery/*" - ], - "cherrypy": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@cherrypy", - "tests/contrib/cherrypy/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "consul": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@consul", - "tests/contrib/consul/*" - ], - "dogpile_cache": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dogpile_cache", - "tests/contrib/dogpile_cache/*" - ], - "dramatiq": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dramatiq", - "tests/contrib/dramatiq/*", - "tests/snapshots/tests.contrib.dramatiq.*" - ], - "elasticsearch": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@elasticsearch", - "tests/contrib/elasticsearch/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "falcon": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@falcon", - "tests/contrib/falcon/*" - ], - "django": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@appsec", - "@appsec_iast", - "@asgi", - "@wsgi", - "@django", - "@dbapi", - "@asgi", - "@pg", - "tests/appsec/*", - "tests/contrib/django/*", - "tests/contrib/django_celery/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "django_hosts": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@appsec", - "@django", - "tests/snapshots/tests.contrib.{suite}.*", - "tests/contrib/django_hosts/*", - "tests/contrib/django_hosts/django_app/*" - ], - "djangorestframework": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@appsec", - "@django", - "tests/contrib/djangorestframework/*", - "tests/contrib/djangorestframework/app/*" - ], - "fastapi": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@starlette", - "@fastapi", - "@asgi", - "@asyncio", - "tests/contrib/fastapi/*", - "tests/snapshots/tests.contrib.starlette.*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "flask": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@flask", - "@appsec", - "@appsec_iast", - "@wsgi", - "@redis", - "tests/appsec/*", - "tests/contrib/flask/*", - "tests/contrib/flask_autopatch/*", - "tests/contrib/flask_cache/*", - "tests/snapshots/tests.contrib.flask.*" - ], - "gevent": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@gevent", - "tests/contrib/gevent/*" - ], - "graphene": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "tests/contrib/graphene/*", - "tests/snapshots/tests.contrib.graphene*" - ], - "graphql": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@graphql", - "tests/contrib/graphql/*", - "tests/snapshots/tests.contrib.graphql.*" - ], - "grpc": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@grpc", - "tests/contrib/grpc/*", - "tests/contrib/grpc_aio/*", - "tests/snapshots/tests.contrib.grpc.*" - ], - "gunicorn": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@gunicorn", - "tests/contrib/gunicorn/*", - "tests/snapshots/tests.contrib.gunicorn.*" - ], - "httplib": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@httplib", - "tests/contrib/httplib/*" - ], - "httpx": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@httpx", - "tests/contrib/httpx/*", - "tests/snapshots/tests.contrib.httpx.*" - ], - "mariadb": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@mariadb", - "tests/contrib/mariadb/*", - "tests/snapshots/tests.contrib.mariadb.*" - ], - "molten": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@molten", - "tests/contrib/molten/*" - ], - "mysqldb": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@dbapi", - "@mysql", - "tests/contrib/mysqldb/*", - "tests/contrib/mysql/*", - "tests/contrib/shared_tests.py" - ], - "pymysql": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@mysql", - "tests/contrib/pymysql/*", - "tests/contrib/mysql/*", - "tests/contrib/shared_tests.py" - ], - "pylibmc": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pylibmc", - "tests/contrib/pylibmc/*" - ], - "pytest": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pytest", - "@ci_visibility", - "@coverage", - "@codeowners", - "tests/contrib/pytest/*", - "tests/contrib/pytest_benchmark/*", - "tests/contrib/pytest_bdd/*", - "tests/snapshots/tests.contrib.pytest.*" - ], - "pytest_v2": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pytest", - "@ci_visibility", - "@coverage", - "@codeowners", - "tests/contrib/pytest/*", - "tests/contrib/pytest_benchmark/*", - "tests/contrib/pytest_bdd/*", - "tests/snapshots/tests.contrib.pytest.*" - ], - "unittest": [ - "@contrib", - "@unittest", - "@ci_visibility", - "@coverage", - "tests/contrib/unittest/*", - "tests/snapshots/tests.contrib.unittest.*" - ], - "asynctest": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "tests/contrib/asynctest/*" - ], - "pymemcache": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pymemcache", - "tests/contrib/pymemcache/*" - ], - "mongoengine": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@mongo", - "tests/contrib/mongoengine/*" - ], - "pymongo": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@mongo", - "tests/contrib/pymongo/*" - ], - "pynamodb": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pynamodb", - "tests/contrib/pynamodb/*" - ], - "pyodbc": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pyodbc", - "@dbapi", - "tests/contrib/pyodbc/*" - ], - "pyramid": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@pyramid", - "tests/contrib/pyramid/*", - "tests/snapshots/tests.contrib.pyramid.*" - ], - "requests": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@requests", - "tests/contrib/requests/*" - ], - "sanic": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@sanic", - "@asyncio", - "tests/contrib/sanic/*", - "tests/snapshots/tests.contrib.sanic.*" - ], - "snowflake": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@snowflake", - "tests/contrib/snowflake/*", - "tests/snapshots/tests.contrib.snowflake.*" - ], - "starlette": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@asyncio", - "@starlette", - "@asgi", - "tests/contrib/starlette/*" - ], - "structlog": [ - "@core", - "@contrib", - "@tracing", - "@structlog", - "tests/contrib/structlog/*" - ], - "sqlalchemy": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@sqlalchemy", - "@dbapi", - "tests/contrib/sqlalchemy/*" - ], - "subprocess": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@subprocess", - "@appsec", - "tests/contrib/subprocess/*" - ], - "psycopg": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@dbapi", - "@pg", - "tests/contrib/psycopg/*", - "tests/snapshots/tests.contrib.psycopg.*", - "tests/contrib/shared_tests.py" - ], - "psycopg2": [ - "@bootstrap", - "@core", - "@contrib", - "@dbapi", - "@tracing", - "@pg", - "tests/contrib/psycopg2/*", - "tests/snapshots/tests.contrib.psycopg2.*", - "tests/contrib/shared_tests.py" - ], - "aiobotocore": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@botocore", - "tests/contrib/aiobotocore/*" - ], - "aiomysql": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@mysql", - "tests/contrib/aiomysql/*", - "tests/snapshots/tests.contrib.{suite}.*", - "tests/contrib/shared_tests_async.py" - ], - "aiopg": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@dbapi", - "@pg", - "@aiopg", - "tests/contrib/aiopg/*", - "tests/contrib/shared_tests_async.py" - ], - "aredis": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@redis", - "tests/contrib/aredis/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "avro": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@avro", - "tests/contrib/avro/*" - ], - "protobuf": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@protobuf", - "tests/contrib/protobuf/*" - ], - "yaaredis": [ - "@core", - "@bootstrap", - "@contrib", - "@tracing", - "@redis", - "tests/contrib/yaaredis/*", - "tests/snapshots/tests.contrib.yaaredis.*" - ], - "redis": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@redis", - "tests/contrib/redis/*", - "tests/snapshots/tests.contrib.redis.*" - ], - "tornado": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@tornado", - "@futures", - "tests/contrib/tornado/*" - ], - "rediscluster": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@redis", - "tests/contrib/rediscluster/*", - "tests/snapshots/tests.contrib.rediscluster.*" - ], - "rq": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@rq", - "tests/contrib/rq/*", - "tests/snapshots/tests.contrib.rq.*" - ], - "urllib3": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@urllib3", - "tests/contrib/urllib3/*", - "tests/snapshots/tests.contrib.urllib3.*" - ], - "webbrowser": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@webbrowser", - "tests/appsec/iast/taint_sinks/test_ssrf.py" - ], - "urllib": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@urllib", - "tests/appsec/iast/taint_sinks/test_ssrf.py" - ], - "vertica": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@vertica", - "tests/contrib/vertica/*" - ], - "wsgi": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@wsgi", - "tests/contrib/wsgi/*", - "tests/contrib/django/test_django_wsgi.py", - "tests/contrib/uwsgi/__init__.py", - "tests/snapshots/tests.contrib.wsgi.*" - ], - "kombu": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@kombu", - "@datastreams", - "tests/contrib/kombu/*" - ], - "jinja2": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@jinja2", - "tests/contrib/jinja2/*" - ], - "mako": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@mako", - "tests/contrib/mako/*" - ], - "algoliasearch": [ - "@bootstrap", - "@core", - "@contrib", - "@tracing", - "@algoliasearch", - "tests/contrib/algoliasearch/*" - ], - "langchain": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@langchain", - "@requests", - "@llmobs", - "tests/contrib/langchain/*", - "tests/snapshots/tests.contrib.{suite}.*" - ], - "anthropic": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@anthropic", - "@requests", - "@llmobs", - "tests/contrib/anthropic/*", - "tests/snapshots/tests.contrib.anthropic.*" - ], - "google_generativeai": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@google_generativeai", - "@llmobs", - "tests/contrib/google_generativeai/*", - "tests/snapshots/tests.contrib.google_generativeai.*" - ], - "vertexai": [ - "@bootstrap", - "@core", - "@tracing", - "@contrib", - "@vertexai", - "@llmobs", - "tests/contrib/vertexai/*", - "tests/snapshots/tests.contrib.vertexai.*" - ], - "runtime": [ - "@core", - "@runtime", - "tests/runtime/*" - ] - } -} diff --git a/tests/README.md b/tests/README.md index 20475da6965..b5ea0d9bf7f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,37 +2,70 @@ This repository makes use of dynamic features of CI providers to run only the jobs that are necessary for the changes in the pull request. This is done by -giving a logical description of the test suites in terms of _components_. A -component is a logical grouping of tests that can be run independently of other +giving a logical description of the test suites in terms of _components_ and _suites_. +These are declared in a modular way in `suitespec.yml` files within the `/tests` +sub-tree. When the CI configuration is generated, these files are aggregated to +build the full test suite specification. + +## Components + +A component is a logical grouping of tests that can be run independently of other components. For example, a component could be a test suite for a specific package, or a test suite for a specific feature, e.g. the tracer, the profiler, -etc... . Components may depend on each other, provide that the resulting -dependency graph is acyclic. For example, the library has a _core_ component -on which most, if not all, other components depend. When a change is made to the -core component, all other components depending on it must be re-tested. This -logical description is contained in the `/tests/.suitespec.json` file. +etc... . Inside a `suitespec.yml` file, a component declares the patterns of +files that should trigger the tests in that component. + +```yaml +components: + tracer: + - ddtrace/tracer/*.py + ... +``` + +Some file patterns need to trigger all the tests in the test suite. This is +generally the case for setup files, such as `setup.py`, `pyproject.toml`, etc... +Tests harness sources too need to trigger all tests in general. To avoid +declaring these patterns, or the component explicitly in the suites, their name +can be prefixed with `$`. Components prefixed with `$` will be applied to _all_ +suites automatically. + +## Suites + +A suite declares what job needs to run when the associated paths are modified. +The suite schema is as follows: + +```yaml + suite_name: + runner: # The test runner (riot | hatch) + skip: # Skip the suite, even when needed + env: # Environment variables to pass to the runner + parallelism: # The parallel degree of the job + retry: # The number of retries for the job + timeout: # The timeout for the job + pattern: # The pattern/environment name (if different from the suite name) + paths: # The paths/components that trigger the job +``` -The jobs to be run are defined in `jobspec.yml` files under the `/tests` -sub-tree. Each entry has the name of a suite from the suitespec file, and -defines the parameters for the runner image. The general structure of a job spec -is +For example ```yaml -suite_name: # A suite name from the .suitespec.json file - runner: # The runner image to use (riot | hatch) - is_snapshot: # Whether this is a snapshot test run (optional, defaults to false) - services: # Any services to spawn for the tests (e.g. redis, optional) - env: # Environment variables to set for the tests - SUITE_NAME: # The suite pattern from the riotfile|hatch env names (mandatory) - parallelism: # The parallel degree of the job (optional, defaults to 1) - retry: # The number of retries for the job (optional) - timeout: # The timeout for the job (optional) +suites: + profile: + runner: riot + env: + DD_TRACE_AGENT_URL: '' + parallelism: 20 + retry: 2 + pattern: profile$|profile-v2 + paths: + - '@bootstrap' + - '@core' + - '@profiling' + - tests/profiling/* + - tests/profiling_v2/* ``` -To create a new job for a new test suite, create a new suite entry in the -`.suitespec.json` file and declare the file patterns that should trigger it. -Then create a new job in a `jobspec.yml` file under the `/tests` sub-tree with -the format described above, matching the name of the suite in the -`.suitespec.json` just created. The `SUITE_NAME` environment variable must be -a regular expression matching at least one riotfile environment (for the riot -runner) or a hatch environment (for the hatch runner). +Components do not need to be declared within the same `suitespec.yml` file. They +can be declared in any file within the `/tests` sub-tree. The CI configuration +generator will aggregate all the components and suites to build the full test +suite specification and resolve the components after that. diff --git a/tests/appsec/jobspec.yml b/tests/appsec/jobspec.yml deleted file mode 100644 index bea6d7b77ec..00000000000 --- a/tests/appsec/jobspec.yml +++ /dev/null @@ -1,65 +0,0 @@ -appsec: - runner: riot - is_snapshot: true - env: - SUITE_NAME: "appsec$" - parallelism: 6 - retry: 2 -appsec_iast: - runner: riot - is_snapshot: true - services: - - postgres - env: - SUITE_NAME: "appsec_iast$" - TEST_POSTGRES_HOST: postgres - parallelism: 6 - retry: 2 - timeout: 25m -appsec_iast_tdd_propagation: - runner: riot - is_snapshot: true - env: - SUITE_NAME: appsec_iast_tdd_propagation - parallelism: 2 - retry: 2 -appsec_iast_memcheck: - runner: riot - is_snapshot: true - env: - SUITE_NAME: appsec_iast_memcheck - CI_DEBUG_TRACE: "true" - PYTEST_ADDOPTS: "-v -s" - parallelism: 4 - retry: 2 -appsec_threats_django: - runner: hatch - env: - SUITE_NAME: appsec_threats_django - parallelism: 12 - retry: 2 -appsec_threats_flask: - runner: hatch - env: - SUITE_NAME: appsec_threats_flask - parallelism: 10 - retry: 2 -appsec_threats_fastapi: - runner: hatch - env: - SUITE_NAME: appsec_threats_fastapi - parallelism: 9 - retry: 2 -appsec_aggregated_leak_testing: - runner: hatch - env: - SUITE_NAME: appsec_aggregated_leak_testing - parallelism: 3 - retry: 2 - timeout: 35m -appsec_iast_native: - runner: hatch - env: - SUITE_NAME: appsec_iast_native - parallelism: 6 - retry: 2 diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml new file mode 100644 index 00000000000..e2b15e2336c --- /dev/null +++ b/tests/appsec/suitespec.yml @@ -0,0 +1,173 @@ +--- +components: + appsec: + - ddtrace/appsec/* + - ddtrace/settings/asm.py + appsec_iast: + - ddtrace/appsec/iast/* + urllib: + - ddtrace/contrib/urllib/* + - ddtrace/contrib/internal/urllib/* + webbrowser: + - ddtrace/contrib/webbrowser/* + - ddtrace/contrib/internal/webbrowser/* +suites: + appsec: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + - tests/appsec/* + pattern: appsec$ + retry: 2 + runner: riot + snapshot: true + appsec_iast: + env: + TEST_POSTGRES_HOST: postgres + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + - tests/appsec/iast/* + pattern: appsec_iast$ + retry: 2 + runner: riot + services: + - postgres + snapshot: true + timeout: 25m + appsec_iast_memcheck: + env: + CI_DEBUG_TRACE: 'true' + PYTEST_ADDOPTS: '-v -s' + parallelism: 4 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + - tests/appsec/iast/* + - tests/appsec/iast_memcheck/* + retry: 2 + runner: riot + snapshot: true + appsec_iast_native: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + retry: 2 + runner: hatch + appsec_iast_packages: + parallelism: 5 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + - tests/appsec/iast/* + - tests/appsec/iast_packages/* + runner: riot + snapshot: true + appsec_integrations: + parallelism: 7 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@remoteconfig' + - tests/appsec/* + - tests/snapshots/tests.appsec.* + retry: 2 + runner: riot + snapshot: true + appsec_threats_django: + parallelism: 12 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@asgi' + - '@wsgi' + - '@django' + - '@remoteconfig' + - tests/appsec/* + - tests/appsec/contrib_appsec/* + retry: 2 + runner: hatch + appsec_threats_fastapi: + parallelism: 9 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@asgi' + - '@wsgi' + - '@fastapi' + - '@starlette' + - '@remoteconfig' + - tests/appsec/* + - tests/appsec/contrib_appsec/* + retry: 2 + runner: hatch + appsec_threats_flask: + parallelism: 10 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@appsec' + - '@appsec_iast' + - '@asgi' + - '@wsgi' + - '@flask' + - '@remoteconfig' + - tests/appsec/* + - tests/appsec/contrib_appsec/* + retry: 2 + runner: hatch + urllib: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@urllib' + - tests/appsec/iast/taint_sinks/test_ssrf.py + runner: riot + skip: true # TODO: No environment available + webbrowser: + # services: + # - pygoat + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@webbrowser' + - tests/appsec/iast/taint_sinks/test_ssrf.py + runner: riot + skip: true # TODO: No environment available diff --git a/tests/ci_visibility/jobspec.yml b/tests/ci_visibility/jobspec.yml deleted file mode 100644 index fc08931eda9..00000000000 --- a/tests/ci_visibility/jobspec.yml +++ /dev/null @@ -1,29 +0,0 @@ -ci_visibility: - runner: riot - is_snapshot: true - env: - SUITE_NAME: ci_visibility - parallelism: 4 -dd_coverage: - runner: hatch - is_snapshot: true - env: - SUITE_NAME: dd_coverage - parallelism: 6 -pytest: - runner: riot - is_snapshot: true - env: - SUITE_NAME: pytest - parallelism: 12 -pytest_v2: - runner: hatch - is_snapshot: true - env: - SUITE_NAME: pytest_plugin_v2 - parallelism: 10 -unittest: - runner: riot - is_snapshot: true - env: - SUITE_NAME: unittest diff --git a/tests/ci_visibility/suitespec.yml b/tests/ci_visibility/suitespec.yml new file mode 100644 index 00000000000..fe69f732c6d --- /dev/null +++ b/tests/ci_visibility/suitespec.yml @@ -0,0 +1,82 @@ +--- +components: + ci_visibility: + - ddtrace/internal/ci_visibility/* + - ddtrace/ext/test_visibility/* + - ddtrace/ext/test.py + dd_coverage: + - ddtrace/internal/coverage/* + pytest: + - ddtrace/contrib/pytest/* + - ddtrace/contrib/pytest_bdd/* + - ddtrace/contrib/pytest_benchmark/* + unittest: + - ddtrace/contrib/unittest/* +suites: + ci_visibility: + parallelism: 4 + paths: + - '@ci_visibility' + - '@ci' + - '@coverage' + - '@git' + - '@pytest' + - '@codeowners' + - tests/ci_visibility/* + runner: riot + snapshot: true + dd_coverage: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@dd_coverage' + - tests/coverage/* + runner: hatch + snapshot: true + pytest: + parallelism: 12 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pytest' + - '@ci_visibility' + - '@coverage' + - '@codeowners' + - tests/contrib/pytest/* + - tests/contrib/pytest_benchmark/* + - tests/contrib/pytest_bdd/* + - tests/snapshots/tests.contrib.pytest.* + runner: riot + snapshot: true + pytest_v2: + parallelism: 10 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pytest' + - '@ci_visibility' + - '@coverage' + - '@codeowners' + - tests/contrib/pytest/* + - tests/contrib/pytest_benchmark/* + - tests/contrib/pytest_bdd/* + - tests/snapshots/tests.contrib.pytest.* + pattern: pytest_plugin_v2 + runner: hatch + snapshot: true + unittest: + paths: + - '@contrib' + - '@unittest' + - '@ci_visibility' + - '@coverage' + - tests/contrib/unittest/* + - tests/snapshots/tests.contrib.unittest.* + runner: riot + snapshot: true diff --git a/tests/contrib/jobspec.yml b/tests/contrib/jobspec.yml deleted file mode 100644 index 6cf1538eff3..00000000000 --- a/tests/contrib/jobspec.yml +++ /dev/null @@ -1,358 +0,0 @@ -aiohttp: - runner: riot - is_snapshot: true - services: - - httpbin_local - env: - SUITE_NAME: aiohttp - parallelism: 3 -algoliasearch: - runner: riot - is_snapshot: true - env: - SUITE_NAME: algoliasearch -aredis: - runner: riot - is_snapshot: true - services: - - redis - env: - SUITE_NAME: "aredis$" - parallelism: 3 -asgi: - runner: riot - is_snapshot: true - env: - SUITE_NAME: "asgi$" -asynctest: - runner: riot - env: - SUITE_NAME: "asynctest$" - parallelism: 3 -avro: - runner: riot - is_snapshot: true - env: - SUITE_NAME: avro -bottle: - runner: riot - is_snapshot: true - env: - SUITE_NAME: bottle - parallelism: 1 -celery: - runner: riot - is_snapshot: true - services: - - rabbitmq - - redis - env: - SUITE_NAME: celery - LOG_LEVEL: DEBUG - PORT: 9126 - DD_POOL_TRACE_CHECK_FAILURES: true - DD_DISABLE_ERROR_RESPONSES: true - ENABLED_CHECKS: trace_stall,meta_tracer_version_header,trace_content_length,trace_peer_service,trace_dd_service # disable flaky content length check -cherrypy: - runner: riot - is_snapshot: true - env: - SUITE_NAME: cherrypy -dogpile_cache: - runner: riot - is_snapshot: true - env: - SUITE_NAME: dogpile_cache -elasticsearch: - runner: riot - is_snapshot: true - services: - - opensearch - - elasticsearch - env: - SUITE_NAME: elasticsearch - TEST_ELASTICSEARCH_HOST: "elasticsearch" - TEST_OPENSEARCH_HOST: "opensearch" - parallelism: 17 -falcon: - runner: riot - is_snapshot: true - env: - SUITE_NAME: falcon -django: - runner: riot - is_snapshot: true - services: - - postgres - - memcached - - redis - env: - SUITE_NAME: "django($|_celery)" - TEST_POSTGRES_HOST: "postgres" - TEST_MEMCACHED_HOST: "memcached" - TEST_REDIS_HOST: "redis" -django_hosts: - runner: riot - is_snapshot: true - env: - SUITE_NAME: "django_hosts$" -djangorestframework: - runner: riot - is_snapshot: true - services: - - memcached - - redis - env: - SUITE_NAME: djangorestframework - TEST_MEMCACHED_HOST: memcached - TEST_REDIS_HOST: redis -dramatiq: - runner: riot - is_snapshot: true - services: - - redis - env: - SUITE_NAME: dramatiq - TEST_REDIS_HOST: redis - parallelism: 2 -fastapi: - runner: riot - is_snapshot: true - env: - SUITE_NAME: fastapi -flask: - runner: riot - is_snapshot: true - services: - - memcached - - redis - env: - SUITE_NAME: flask - TEST_MEMCACHED_HOST: memcached - TEST_REDIS_HOST: redis - parallelism: 14 -gevent: - runner: riot - is_snapshot: true - env: - SUITE_NAME: gevent -graphene: - runner: riot - is_snapshot: true - env: - SUITE_NAME: graphene -graphql: - runner: riot - is_snapshot: true - env: - SUITE_NAME: graphql - parallelism: 6 -grpc: - runner: riot - is_snapshot: true - env: - SUITE_NAME: grpc -gunicorn: - runner: riot - is_snapshot: true - env: - SUITE_NAME: gunicorn - parallelism: 12 -httplib: - runner: riot - is_snapshot: true - services: - - httpbin_local - env: - SUITE_NAME: httplib -httpx: - runner: riot - is_snapshot: true - services: - - httpbin_local - env: - SUITE_NAME: httpx - parallelism: 2 -jinja2: - runner: riot - is_snapshot: true - env: - SUITE_NAME: jinja2 -kafka: - runner: riot - is_snapshot: true - services: - - kafka - env: - SUITE_NAME: kafka - TEST_KAFKA_HOST: "kafka" - TEST_KAFKA_PORT: "29092" - parallelism: 4 -kombu: - runner: riot - is_snapshot: true - services: - - rabbitmq - env: - SUITE_NAME: kombu -logbook: - runner: riot - is_snapshot: true - env: - SUITE_NAME: logbook -loguru: - runner: riot - is_snapshot: true - env: - SUITE_NAME: loguru -mako: - runner: riot - is_snapshot: true - env: - SUITE_NAME: mako -molten: - runner: riot - is_snapshot: true - env: - SUITE_NAME: molten -opentracer: - runner: riot - env: - SUITE_NAME: opentracer -protobuf: - runner: riot - is_snapshot: true - env: - SUITE_NAME: protobuf -pylibmc: - runner: riot - is_snapshot: true - services: - - memcached - env: - SUITE_NAME: pylibmc -pymemcache: - runner: riot - is_snapshot: true - services: - - memcached - env: - SUITE_NAME: pymemcache -pymongo: - runner: riot - is_snapshot: true - services: - - mongo - env: - SUITE_NAME: pymongo -pynamodb: - runner: riot - is_snapshot: true - env: - SUITE_NAME: pynamodb -pyodbc: - runner: riot - is_snapshot: true - env: - SUITE_NAME: pyodbc -pyramid: - runner: riot - is_snapshot: true - env: - SUITE_NAME: pyramid -redis: - runner: riot - is_snapshot: true - services: - - rediscluster - - redis - env: - SUITE_NAME: "^redis$" - parallelism: 5 -rediscluster: - runner: riot - is_snapshot: true - services: - - redis - - rediscluster - env: - SUITE_NAME: rediscluster -requests: - runner: riot - is_snapshot: true - services: - - httpbin_local - env: - SUITE_NAME: requests -rq: - runner: riot - is_snapshot: true - services: - - redis - env: - SUITE_NAME: rq - parallelism: 2 -sanic: - runner: riot - is_snapshot: true - env: - SUITE_NAME: sanic -snowflake: - runner: riot - is_snapshot: true - env: - SUITE_NAME: snowflake -sourcecode: - runner: riot - env: - SUITE_NAME: sourcecode -starlette: - runner: riot - is_snapshot: true - env: - SUITE_NAME: starlette -stdlib: - runner: riot - is_snapshot: true - env: - SUITE_NAME: 'asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$' -structlog: - runner: riot - is_snapshot: true - env: - SUITE_NAME: structlog -subprocess: - runner: riot - env: - SUITE_NAME: subprocess -test_logging: - runner: riot - is_snapshot: true - env: - SUITE_NAME: test_logging -tornado: - runner: riot - is_snapshot: true - env: - SUITE_NAME: tornado -urllib3: - runner: riot - is_snapshot: true - services: - - httpbin_local - env: - SUITE_NAME: urllib3 - TEST_HTTPBIN_HOST: "httpbin-local" - TEST_HTTPBIN_PORT: "8001" -wsgi: - runner: riot - is_snapshot: true - env: - SUITE_NAME: wsgi -yaaredis: - runner: riot - is_snapshot: true - services: - - redis - env: - SUITE_NAME: "yaaredis$" \ No newline at end of file diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml new file mode 100644 index 00000000000..fd8ac2c4e08 --- /dev/null +++ b/tests/contrib/suitespec.yml @@ -0,0 +1,1152 @@ +--- +components: + aiohttp: + - ddtrace/contrib/aiohttp/* + - ddtrace/contrib/internal/aiohttp/* + - ddtrace/contrib/aiohttp_jinja2/* + - ddtrace/contrib/internal/aiohttp_jinja2/* + aiopg: + - ddtrace/contrib/aiopg/* + - ddtrace/contrib/internal/aiopg/* + algoliasearch: + - ddtrace/contrib/algoliasearch/* + - ddtrace/contrib/internal/algoliasearch/* + asgi: + - ddtrace/contrib/asgi/* + - ddtrace/contrib/internal/asgi/* + asyncio: + - ddtrace/contrib/asyncio/* + avro: + - ddtrace/contrib/avro/* + - ddtrace/contrib/internal/avro/* + aws_lambda: + - ddtrace/contrib/aws_lambda/* + - ddtrace/contrib/internal/aws_lambda/* + - ddtrace/ext/aws.py + botocore: + - ddtrace/contrib/botocore/* + - ddtrace/contrib/internal/botocore/* + - ddtrace/contrib/boto/* + - ddtrace/contrib/internal/boto/* + - ddtrace/contrib/aiobotocore/* + - ddtrace/contrib/internal/aiobotocore/* + bottle: + - ddtrace/contrib/bottle/* + - ddtrace/contrib/internal/bottle/* + cassandra: + - ddtrace/contrib/cassandra/* + - ddtrace/contrib/internal/cassandra/* + - ddtrace/ext/cassandra.py + celery: + - ddtrace/contrib/celery/* + - ddtrace/contrib/internal/celery/* + cherrypy: + - ddtrace/contrib/cherrypy/* + - ddtrace/contrib/internal/cherrypy/* + consul: + - ddtrace/contrib/consul/* + - ddtrace/contrib/internal/consul/* + - ddtrace/ext/consul.py + contrib: + - ddtrace/contrib/__init__.py + - ddtrace/contrib/trace_utils.py + - ddtrace/contrib/trace_utils_async.py + - ddtrace/contrib/internal/* + - ddtrace/ext/__init__.py + - ddtrace/ext/http.py + - ddtrace/ext/net.py + - ddtrace/ext/schema.py + - ddtrace/ext/sql.py + - ddtrace/ext/test.py + - ddtrace/ext/user.py + - ddtrace/propagation/* + - ddtrace/settings/_database_monitoring.py + - tests/contrib/patch.py + - tests/contrib/config.py + - tests/contrib/__init__.py + - tests/contrib/suitespec.yml + coverage: + - ddtrace/contrib/coverage/* + - ddtrace/contrib/internal/coverage/* + dbapi: + - ddtrace/contrib/dbapi/* + - ddtrace/contrib/dbapi_async/* + - ddtrace/ext/db.py + django: + - ddtrace/contrib/django/* + - ddtrace/contrib/internal/django/* + dogpile_cache: + - ddtrace/contrib/dogpile_cache/* + - ddtrace/contrib/internal/dogpile_cache/* + dramatiq: + - ddtrace/contrib/dramatiq/* + - ddtrace/contrib/internal/dramatiq/* + elasticsearch: + - ddtrace/contrib/elasticsearch/* + - ddtrace/contrib/internal/elasticsearch/* + - ddtrace/ext/elasticsearch.py + falcon: + - ddtrace/contrib/falcon/* + - ddtrace/contrib/internal/falcon/* + fastapi: + - ddtrace/contrib/fastapi/* + - ddtrace/contrib/internal/fastapi/* + flask: + - ddtrace/contrib/flask/* + - ddtrace/contrib/internal/flask/* + - ddtrace/contrib/flask_cache/* + - ddtrace/contrib/internal/flask_cache/* + - ddtrace/contrib/flask_login/* + futures: + - ddtrace/contrib/futures/* + - ddtrace/contrib/internal/futures/* + gevent: + - ddtrace/contrib/gevent/* + - ddtrace/contrib/internal/gevent/* + graphql: + - ddtrace/contrib/graphql/* + - ddtrace/contrib/internal/graphql/* + grpc: + - ddtrace/contrib/grpc/* + - ddtrace/contrib/internal/grpc/* + gunicorn: + - ddtrace/contrib/gunicorn/* + httplib: + - ddtrace/contrib/httplib/* + - ddtrace/contrib/internal/httplib/* + httpx: + - ddtrace/contrib/httpx/* + - ddtrace/contrib/internal/httpx/* + jinja2: + - ddtrace/contrib/jinja2/* + - ddtrace/contrib/internal/jinja2/* + kafka: + - ddtrace/contrib/kafka/* + - ddtrace/contrib/internal/kafka/* + - ddtrace/ext/kafka.py + kombu: + - ddtrace/contrib/kombu/* + - ddtrace/contrib/internal/kombu/* + - ddtrace/ext/kombu.py + logbook: + - ddtrace/contrib/logbook/* + - ddtrace/contrib/internal/logbook/* + logging: + - ddtrace/contrib/logging/* + - ddtrace/contrib/internal/logging/* + loguru: + - ddtrace/contrib/logging/* + - ddtrace/contrib/internal/logging/* + - ddtrace/contrib/loguru/* + - ddtrace/contrib/internal/loguru/* + mako: + - ddtrace/contrib/mako/* + - ddtrace/contrib/internal/mako/* + mariadb: + - ddtrace/contrib/mariadb/* + - ddtrace/contrib/internal/mariadb/* + molten: + - ddtrace/contrib/molten/* + - ddtrace/contrib/internal/molten/* + mongo: + - ddtrace/contrib/pymongo/* + - ddtrace/contrib/internal/pymongo/* + - ddtrace/contrib/mongoengine/* + - ddtrace/contrib/internal/mongoengine/* + - ddtrace/ext/mongo.py + mysql: + - ddtrace/contrib/mysql/* + - ddtrace/contrib/internal/mysql/* + - ddtrace/contrib/mysqldb/* + - ddtrace/contrib/internal/mysqldb/* + - ddtrace/contrib/pymysql/* + - ddtrace/contrib/internal/pymysql/* + - ddtrace/contrib/aiomysql/* + - ddtrace/contrib/internal/aiomysql/* + pg: + - ddtrace/contrib/aiopg/* + - ddtrace/contrib/internal/aiopg/* + - ddtrace/contrib/asyncpg/* + - ddtrace/contrib/internal/psycopg/* + - ddtrace/contrib/internal/asyncpg/* + - ddtrace/contrib/psycopg/* + protobuf: + - ddtrace/contrib/protobuf/* + - ddtrace/contrib/internal/protobuf/* + pylibmc: + - ddtrace/contrib/pylibmc/* + - ddtrace/contrib/internal/pylibmc/* + pymemcache: + - ddtrace/contrib/pymemcache/* + - ddtrace/contrib/internal/pymemcache/* + - ddtrace/ext/memcached.py + pynamodb: + - ddtrace/contrib/pynamodb/* + - ddtrace/contrib/internal/pynamodb/* + pyodbc: + - ddtrace/contrib/pyodbc/* + - ddtrace/contrib/internal/pyodbc/* + pyramid: + - ddtrace/contrib/pyramid/* + - ddtrace/contrib/internal/pyramid/* + redis: + - ddtrace/contrib/rediscluster/* + - ddtrace/contrib/internal/rediscluster/* + - ddtrace/contrib/redis/* + - ddtrace/contrib/internal/redis/* + - ddtrace/contrib/aredis/* + - ddtrace/contrib/internal/aredis/* + - ddtrace/contrib/yaaredis/* + - ddtrace/contrib/internal/yaaredis/* + - ddtrace/_trace/utils_redis.py + - ddtrace/contrib/redis_utils.py + - ddtrace/contrib/trace_utils_redis.py + - ddtrace/ext/redis.py + requests: + - ddtrace/contrib/requests/* + - ddtrace/contrib/internal/requests/* + rq: + - ddtrace/contrib/rq/* + sanic: + - ddtrace/contrib/sanic/* + - ddtrace/contrib/internal/sanic/* + snowflake: + - ddtrace/contrib/snowflake/* + - ddtrace/contrib/internal/snowflake/* + sqlalchemy: + - ddtrace/contrib/sqlalchemy/* + - ddtrace/contrib/internal/sqlalchemy/* + sqlite3: + - ddtrace/contrib/sqlite3/* + - ddtrace/contrib/internal/sqlite3/* + starlette: + - ddtrace/contrib/starlette/* + - ddtrace/contrib/internal/starlette/* + structlog: + - ddtrace/contrib/structlog/* + - ddtrace/contrib/internal/structlog/* + subprocess: + - ddtrace/contrib/subprocess/* + - ddtrace/contrib/internal/subprocess/* + tornado: + - ddtrace/contrib/tornado/* + - ddtrace/contrib/internal/tornado/* + urllib3: + - ddtrace/contrib/urllib3/* + - ddtrace/contrib/internal/urllib3/* + vertica: + - ddtrace/contrib/vertica/* + - ddtrace/contrib/internal/vertica/* + wsgi: + - ddtrace/contrib/wsgi/* + - ddtrace/contrib/internal/wsgi/* +suites: + aiobotocore: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@botocore' + - tests/contrib/aiobotocore/* + runner: riot + aiohttp: + parallelism: 3 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@aiohttp' + - tests/contrib/aiohttp/* + - tests/contrib/aiohttp_jinja2/* + - tests/snapshots/tests.contrib.aiohttp_jinja2.* + - tests/snapshots/tests.{suite}.* + runner: riot + services: + - httpbin_local + snapshot: true + aiomysql: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dbapi' + - '@mysql' + - tests/contrib/aiomysql/* + - tests/snapshots/tests.{suite}.* + - tests/contrib/shared_tests_async.py + runner: riot + aiopg: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dbapi' + - '@pg' + - '@aiopg' + - tests/contrib/aiopg/* + - tests/contrib/shared_tests_async.py + runner: riot + algoliasearch: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@algoliasearch' + - tests/contrib/algoliasearch/* + runner: riot + snapshot: true + aredis: + parallelism: 3 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@redis' + - tests/contrib/aredis/* + - tests/snapshots/tests.{suite}.* + pattern: aredis$ + runner: riot + services: + - redis + snapshot: true + asgi: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@asyncio' + - '@appsec' + - '@asgi' + - tests/contrib/asgi/* + pattern: asgi$ + runner: riot + snapshot: true + asyncpg: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pg' + - tests/contrib/asyncpg/* + - tests/snapshots/tests.{suite}.* + - tests/contrib/shared_tests_async.py + runner: riot + snapshot: true + asynctest: + parallelism: 3 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - tests/contrib/asynctest/* + pattern: asynctest$ + runner: riot + avro: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@avro' + - tests/contrib/avro/* + runner: riot + snapshot: true + aws_lambda: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@aws_lambda' + - tests/contrib/aws_lambda/* + - tests/snapshots/tests.{suite}.* + runner: riot + snapshot: true + botocore: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@llmobs' + - '@botocore' + - '@llmobs' + - '@datastreams' + - tests/contrib/botocore/* + - tests/contrib/boto/* + - tests/snapshots/tests.contrib.botocore* + pattern: ^botocore$ + runner: riot + snapshot: true + bottle: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@bottle' + - tests/contrib/bottle/* + runner: riot + snapshot: true + cassandra: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@cassandra' + - tests/contrib/cassandra/* + runner: riot + celery: + env: + DD_DISABLE_ERROR_RESPONSES: true + DD_POOL_TRACE_CHECK_FAILURES: true + ENABLED_CHECKS: trace_stall,meta_tracer_version_header,trace_content_length,trace_peer_service,trace_dd_service + LOG_LEVEL: DEBUG + PORT: 9126 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@celery' + - tests/contrib/celery/* + runner: riot + services: + - rabbitmq + - redis + snapshot: true + cherrypy: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@cherrypy' + - tests/contrib/cherrypy/* + - tests/snapshots/tests.{suite}.* + runner: riot + snapshot: true + consul: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@consul' + - tests/contrib/consul/* + runner: riot + datastreams: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@datastreams' + - tests/datastreams/* + runner: riot + django: + env: + TEST_MEMCACHED_HOST: memcached + TEST_POSTGRES_HOST: postgres + TEST_REDIS_HOST: redis + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@appsec' + - '@appsec_iast' + - '@asgi' + - '@wsgi' + - '@django' + - '@dbapi' + - '@asgi' + - '@pg' + - tests/appsec/* + - tests/contrib/django/* + - tests/contrib/django_celery/* + - tests/snapshots/tests.{suite}.* + pattern: django($|_celery) + runner: riot + services: + - postgres + - memcached + - redis + snapshot: true + django_hosts: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@appsec' + - '@django' + - tests/snapshots/tests.{suite}.* + - tests/contrib/django_hosts/* + - tests/contrib/django_hosts/django_app/* + pattern: django_hosts$ + runner: riot + snapshot: true + djangorestframework: + env: + TEST_MEMCACHED_HOST: memcached + TEST_REDIS_HOST: redis + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@appsec' + - '@django' + - tests/contrib/djangorestframework/* + - tests/contrib/djangorestframework/app/* + runner: riot + services: + - memcached + - redis + snapshot: true + dogpile_cache: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dogpile_cache' + - tests/contrib/dogpile_cache/* + runner: riot + snapshot: true + dramatiq: + env: + TEST_REDIS_HOST: redis + parallelism: 2 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dramatiq' + - tests/contrib/dramatiq/* + - tests/snapshots/tests.contrib.dramatiq.* + runner: riot + services: + - redis + snapshot: true + elasticsearch: + env: + TEST_ELASTICSEARCH_HOST: elasticsearch + TEST_OPENSEARCH_HOST: opensearch + parallelism: 17 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@elasticsearch' + - tests/contrib/elasticsearch/* + - tests/snapshots/tests.{suite}.* + runner: riot + services: + - opensearch + - elasticsearch + snapshot: true + falcon: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@falcon' + - tests/contrib/falcon/* + runner: riot + snapshot: true + fastapi: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@starlette' + - '@fastapi' + - '@asgi' + - '@asyncio' + - tests/contrib/fastapi/* + - tests/snapshots/tests.contrib.starlette.* + - tests/snapshots/tests.{suite}.* + runner: riot + snapshot: true + flask: + env: + TEST_MEMCACHED_HOST: memcached + TEST_REDIS_HOST: redis + parallelism: 14 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@flask' + - '@appsec' + - '@appsec_iast' + - '@wsgi' + - '@redis' + - tests/appsec/* + - tests/contrib/flask/* + - tests/contrib/flask_autopatch/* + - tests/contrib/flask_cache/* + - tests/snapshots/tests.contrib.flask.* + runner: riot + services: + - memcached + - redis + snapshot: true + gevent: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@gevent' + - tests/contrib/gevent/* + runner: riot + snapshot: true + graphene: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - tests/contrib/graphene/* + - tests/snapshots/tests.contrib.graphene* + runner: riot + snapshot: true + graphql: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@graphql' + - tests/contrib/graphql/* + - tests/snapshots/tests.contrib.graphql.* + runner: riot + snapshot: true + grpc: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@grpc' + - tests/contrib/grpc/* + - tests/contrib/grpc_aio/* + - tests/snapshots/tests.contrib.grpc.* + runner: riot + snapshot: true + gunicorn: + parallelism: 12 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@gunicorn' + - tests/contrib/gunicorn/* + - tests/snapshots/tests.contrib.gunicorn.* + runner: riot + snapshot: true + httplib: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@httplib' + - tests/contrib/httplib/* + runner: riot + services: + - httpbin_local + snapshot: true + httpx: + parallelism: 2 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@httpx' + - tests/contrib/httpx/* + - tests/snapshots/tests.contrib.httpx.* + runner: riot + services: + - httpbin_local + snapshot: true + jinja2: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@jinja2' + - tests/contrib/jinja2/* + runner: riot + snapshot: true + kafka: + env: + TEST_KAFKA_HOST: kafka + TEST_KAFKA_PORT: '29092' + parallelism: 4 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@kafka' + - '@datastreams' + - tests/contrib/kafka/* + - tests/snapshots/tests.contrib.kafka.* + runner: riot + services: + - kafka + snapshot: true + kombu: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@kombu' + - '@datastreams' + - tests/contrib/kombu/* + runner: riot + services: + - rabbitmq + snapshot: true + logbook: + paths: + - '@core' + - '@contrib' + - '@tracing' + - '@logbook' + - tests/contrib/logbook/* + runner: riot + snapshot: true + loguru: + paths: + - '@core' + - '@contrib' + - '@tracing' + - '@loguru' + - tests/contrib/loguru/* + runner: riot + snapshot: true + mako: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@mako' + - tests/contrib/mako/* + runner: riot + snapshot: true + mariadb: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dbapi' + - '@mariadb' + - tests/contrib/mariadb/* + - tests/snapshots/tests.contrib.mariadb.* + runner: riot + molten: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@molten' + - tests/contrib/molten/* + runner: riot + snapshot: true + mongoengine: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@mongo' + - tests/contrib/mongoengine/* + runner: riot + mysqlpython: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@dbapi' + - '@mysql' + - tests/contrib/mysqldb/* + - tests/contrib/mysql/* + - tests/contrib/shared_tests.py + pattern: mysqldb$ + runner: riot + skip: true + opentelemetry: + parallelism: 4 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@opentelemetry' + - tests/opentelemetry/* + - tests/snapshots/tests.opentelemetry.* + runner: riot + snapshot: true + opentracer: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@opentracer' + - tests/opentracer/* + runner: riot + protobuf: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@protobuf' + - tests/contrib/protobuf/* + runner: riot + snapshot: true + psycopg: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@dbapi' + - '@pg' + - tests/contrib/psycopg/* + - tests/contrib/psycopg2/* + - tests/snapshots/tests.contrib.psycopg.* + - tests/snapshots/tests.contrib.psycopg2.* + - tests/contrib/shared_tests.py + runner: riot + pylibmc: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pylibmc' + - tests/contrib/pylibmc/* + runner: riot + services: + - memcached + snapshot: true + pymemcache: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pymemcache' + - tests/contrib/pymemcache/* + runner: riot + services: + - memcached + snapshot: true + pymongo: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@mongo' + - tests/contrib/pymongo/* + runner: riot + services: + - mongo + snapshot: true + pymysql: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dbapi' + - '@mysql' + - tests/contrib/pymysql/* + - tests/contrib/mysql/* + - tests/contrib/shared_tests.py + runner: riot + pynamodb: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pynamodb' + - tests/contrib/pynamodb/* + runner: riot + snapshot: true + pyodbc: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pyodbc' + - '@dbapi' + - tests/contrib/pyodbc/* + runner: riot + snapshot: true + pyramid: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@pyramid' + - tests/contrib/pyramid/* + - tests/snapshots/tests.contrib.pyramid.* + runner: riot + snapshot: true + redis: + parallelism: 5 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@redis' + - tests/contrib/redis/* + - tests/snapshots/tests.contrib.redis.* + pattern: ^redis$ + runner: riot + services: + - rediscluster + - redis + snapshot: true + rediscluster: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@redis' + - tests/contrib/rediscluster/* + - tests/snapshots/tests.contrib.rediscluster.* + runner: riot + services: + - redis + - rediscluster + snapshot: true + requests: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@requests' + - tests/contrib/requests/* + runner: riot + services: + - httpbin_local + snapshot: true + rq: + parallelism: 2 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@rq' + - tests/contrib/rq/* + - tests/snapshots/tests.contrib.rq.* + runner: riot + services: + - redis + snapshot: true + sanic: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@sanic' + - '@asyncio' + - tests/contrib/sanic/* + - tests/snapshots/tests.contrib.sanic.* + runner: riot + snapshot: true + snowflake: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@dbapi' + - '@snowflake' + - tests/contrib/snowflake/* + - tests/snapshots/tests.contrib.snowflake.* + runner: riot + snapshot: true + sourcecode: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@sourcecode' + - tests/sourcecode/* + runner: riot + sqlalchemy: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@sqlalchemy' + - '@dbapi' + - tests/contrib/sqlalchemy/* + runner: riot + starlette: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@asyncio' + - '@starlette' + - '@asgi' + - tests/contrib/starlette/* + runner: riot + snapshot: true + stdlib: + paths: + - '@tracing' + - '@bootstrap' + - '@core' + - '@logging' + - '@asyncio' + - '@futures' + - '@sqlite3' + - '@dbapi' + - '@contrib' + - tests/contrib/logging/* + - tests/contrib/sqlite3/* + - tests/contrib/asyncio/* + - tests/contrib/futures/* + - tests/contrib/dbapi/* + - tests/contrib/dbapi_async/* + pattern: asyncio$|sqlite3$|futures$|dbapi$|dbapi_async$ + runner: riot + snapshot: true + structlog: + paths: + - '@core' + - '@contrib' + - '@tracing' + - '@structlog' + - tests/contrib/structlog/* + runner: riot + snapshot: true + subprocess: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@subprocess' + - '@appsec' + - tests/contrib/subprocess/* + runner: riot + test_logging: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@logging' + - tests/contrib/logging/* + runner: riot + snapshot: true + tornado: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@tornado' + - '@futures' + - tests/contrib/tornado/* + runner: riot + snapshot: true + urllib3: + env: + TEST_HTTPBIN_HOST: httpbin-local + TEST_HTTPBIN_PORT: '8001' + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@urllib3' + - tests/contrib/urllib3/* + - tests/snapshots/tests.contrib.urllib3.* + runner: riot + services: + - httpbin_local + snapshot: true + vertica: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@vertica' + - tests/contrib/vertica/* + runner: riot + skip: true # Vertica tests are flaky + wsgi: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@wsgi' + - tests/contrib/wsgi/* + - tests/contrib/django/test_django_wsgi.py + - tests/contrib/uwsgi/__init__.py + - tests/snapshots/tests.contrib.wsgi.* + runner: riot + snapshot: true + yaaredis: + paths: + - '@core' + - '@bootstrap' + - '@contrib' + - '@tracing' + - '@redis' + - tests/contrib/yaaredis/* + - tests/snapshots/tests.contrib.yaaredis.* + pattern: yaaredis$ + runner: riot + services: + - redis + snapshot: true diff --git a/tests/debugging/jobspec.yml b/tests/debugging/jobspec.yml deleted file mode 100644 index 0e306aa642b..00000000000 --- a/tests/debugging/jobspec.yml +++ /dev/null @@ -1,5 +0,0 @@ -debugger: - runner: riot - env: - SUITE_NAME: debugger - parallelism: 2 diff --git a/tests/debugging/suitespec.yml b/tests/debugging/suitespec.yml new file mode 100644 index 00000000000..3475813c926 --- /dev/null +++ b/tests/debugging/suitespec.yml @@ -0,0 +1,17 @@ +--- +components: + debugging: + - ddtrace/debugging/* + - ddtrace/settings/dynamic_instrumentation.py + - ddtrace/settings/exception_replay.py +suites: + debugger: + parallelism: 2 + paths: + - '@debugging' + - '@bootstrap' + - '@core' + - '@remoteconfig' + - '@tracing' + - tests/debugging/* + runner: riot diff --git a/tests/jobspec.yml b/tests/jobspec.yml deleted file mode 100644 index 0005344a045..00000000000 --- a/tests/jobspec.yml +++ /dev/null @@ -1,46 +0,0 @@ -internal: - runner: riot - is_snapshot: true - env: - SUITE_NAME: internal -tracer: - runner: riot - env: - SUITE_NAME: tracer - DD_TRACE_AGENT_URL: "http://localhost:8126" - parallelism: 9 - retry: 2 -telemetry: - runner: riot - is_snapshot: true - env: - SUITE_NAME: telemetry - parallelism: 6 -profile: - runner: riot - env: - SUITE_NAME: "profile$|profile-v2" - DD_TRACE_AGENT_URL: "" - parallelism: 20 - retry: 2 -integration_testagent: - runner: riot - is_snapshot: true - env: - SUITE_NAME: "integration-snapshot*" -integration_agent: - runner: riot - env: - SUITE_NAME: "integration-latest*" -vendor: - runner: riot - env: - SUITE_NAME: vendor - parallelism: 1 -ddtracerun: - runner: riot - services: - - redis - env: - SUITE_NAME: ddtracerun - parallelism: 6 diff --git a/tests/llmobs/jobspec.yml b/tests/llmobs/jobspec.yml deleted file mode 100644 index 074e30ea975..00000000000 --- a/tests/llmobs/jobspec.yml +++ /dev/null @@ -1,32 +0,0 @@ -llmobs: - runner: riot - env: - SUITE_NAME: llmobs -openai: - runner: riot - is_snapshot: true - env: - SUITE_NAME: openai - parallelism: 10 -langchain: - runner: riot - is_snapshot: true - env: - SUITE_NAME: langchain - parallelism: 6 -anthropic: - runner: riot - is_snapshot: true - env: - SUITE_NAME: anthropic - parallelism: 3 -google_generativeai: - runner: riot - is_snapshot: true - env: - SUITE_NAME: google_generativeai -vertexai: - runner: riot - is_snapshot: true - env: - SUITE_NAME: vertexai \ No newline at end of file diff --git a/tests/llmobs/suitespec.yml b/tests/llmobs/suitespec.yml new file mode 100644 index 00000000000..8e99d1ad6cb --- /dev/null +++ b/tests/llmobs/suitespec.yml @@ -0,0 +1,94 @@ +--- +components: + anthropic: + - ddtrace/contrib/anthropic/* + - ddtrace/contrib/internal/anthropic/* + google_generativeai: + - ddtrace/contrib/google_generativeai/* + - ddtrace/contrib/internal/google_generativeai/* + vertexai: + - ddtrace/contrib/vertexai/* + - ddtrace/contrib/internal/vertexai/* + langchain: + - ddtrace/contrib/langchain/* + - ddtrace/contrib/internal/langchain/* + llmobs: + - ddtrace/llmobs/* + openai: + - ddtrace/contrib/openai/* + - ddtrace/contrib/internal/openai/* +suites: + anthropic: + parallelism: 3 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@anthropic' + - '@requests' + - '@llmobs' + - tests/contrib/anthropic/* + - tests/snapshots/tests.contrib.anthropic.* + runner: riot + snapshot: true + google_generativeai: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@google_generativeai' + - '@llmobs' + - tests/contrib/google_generativeai/* + - tests/snapshots/tests.contrib.google_generativeai.* + runner: riot + snapshot: true + vertexai: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@vertexai' + - '@llmobs' + - tests/contrib/vertexai/* + - tests/snapshots/tests.contrib.vertexai.* + runner: riot + snapshot: true + langchain: + parallelism: 6 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@langchain' + - '@requests' + - '@llmobs' + - tests/contrib/langchain/* + - tests/snapshots/tests.contrib.langchain.* + runner: riot + snapshot: true + llmobs: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@llmobs' + - tests/llmobs/* + runner: riot + openai: + parallelism: 10 + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@contrib' + - '@openai' + - '@requests' + - '@llmobs' + - tests/contrib/openai/* + - tests/snapshots/tests.contrib.openai.* + runner: riot + snapshot: true diff --git a/tests/suitespec.py b/tests/suitespec.py index 37df9277e6b..381f80ebf00 100644 --- a/tests/suitespec.py +++ b/tests/suitespec.py @@ -1,11 +1,38 @@ from functools import cache -import json from pathlib import Path import typing as t +from ruamel.yaml import YAML # noqa -SUITESPECFILE = Path(__file__).parents[1] / "tests" / ".suitespec.json" -SUITESPEC = json.loads(SUITESPECFILE.read_text()) + +TESTS = Path(__file__).parents[1] / "tests" + + +def _collect_suitespecs() -> dict: + # Recursively search for suitespec.yml in TESTS + suitespec = {"components": {}, "suites": {}} + + for s in TESTS.rglob("suitespec.yml"): + try: + namespace = ".".join(s.relative_to(TESTS).parts[:-1]) or None + except IndexError: + namespace = None + with YAML() as yaml: + data = yaml.load(s) + suites = data.get("suites", {}) + if namespace is not None: + for name, spec in list(suites.items()): + if "pattern" not in spec: + spec["pattern"] = name + suites[f"{namespace}::{name}"] = spec + del suites[name] + for k, v in suitespec.items(): + v.update(data.get(k, {})) + + return suitespec + + +SUITESPEC = _collect_suitespecs() @cache @@ -13,7 +40,7 @@ def get_patterns(suite: str) -> t.Set[str]: """Get the patterns for a suite >>> SUITESPEC["components"] = {"$h": ["tests/s.py"], "core": ["core/*"], "debugging": ["ddtrace/d/*"]} - >>> SUITESPEC["suites"] = {"debugger": ["@core", "@debugging", "tests/d/*"]} + >>> SUITESPEC["suites"] = {"debugger": {"paths": ["@core", "@debugging", "tests/d/*"]}} >>> sorted(get_patterns("debugger")) # doctest: +NORMALIZE_WHITESPACE ['core/*', 'ddtrace/d/*', 'tests/d/*', 'tests/s.py'] >>> get_patterns("foobar") @@ -23,7 +50,7 @@ def get_patterns(suite: str) -> t.Set[str]: if suite not in SUITESPEC["suites"]: return set() - suite_patterns = set(SUITESPEC["suites"][suite]) + suite_patterns = set(SUITESPEC["suites"][suite]["paths"]) # Include patterns from include-always components for patterns in (patterns for compo, patterns in compos.items() if compo.startswith("$")): @@ -42,9 +69,14 @@ def resolve(patterns: set) -> set: return resolved_patterns - return {_.format(suite=suite) for _ in resolve(suite_patterns)} + return {_.format(suite=suite.replace("::", ".")) for _ in resolve(suite_patterns)} -def get_suites() -> t.Set[str]: +def get_suites() -> t.Dict[str, dict]: """Get the list of suites.""" - return set(SUITESPEC["suites"].keys()) + return SUITESPEC["suites"] + + +def get_components() -> t.Dict[str, t.List[str]]: + """Get the list of jobs.""" + return SUITESPEC.get("components", {}) diff --git a/tests/suitespec.yml b/tests/suitespec.yml new file mode 100644 index 00000000000..c6a89720676 --- /dev/null +++ b/tests/suitespec.yml @@ -0,0 +1,255 @@ +--- +components: + $harness: + - docker/* + - docker-compose.yml + - riotfile.py + - .riot/requirements/* + - scripts/ddtest + - scripts/run-test-suite + - hatch.toml + - tests/conftest.py + - tests/utils.py + - tests/__init__.py + - tests/suitespec.yml + - tests/suitespec.py + - .circleci/* + - tests/meta/* + - tests/smoke_test.py + - tests/subprocesstest.py + - tests/wait-for-services.py + - tests/webclient.py + - tests/test_module/* + - .gitlab-ci.yml + - .gitlab/* + - tests/suitespec.yml + $setup: + - setup.py + - setup.cfg + - pyproject.toml + bootstrap: + - ddtrace/bootstrap/* + - ddtrace/commands/* + - ddtrace/auto.py + ci: + - ddtrace/ext/ci.py + codeowners: + - ddtrace/internal/codeowners.py + core: + - ddtrace/internal/__init__.py + - ddtrace/internal/_exceptions.py + - ddtrace/internal/_file_queue.py + - ddtrace/internal/_rand.pyi + - ddtrace/internal/_rand.pyx + - ddtrace/internal/_stdint.h + - ddtrace/internal/_threads.* + - ddtrace/internal/_unpatched.py + - ddtrace/internal/agent.py + - ddtrace/internal/assembly.py + - ddtrace/internal/atexit.py + - ddtrace/internal/compat.py + - ddtrace/internal/core/* + - ddtrace/internal/datadog/__init__.py + - ddtrace/internal/debug.py + - ddtrace/internal/dogstatsd.py + - ddtrace/internal/forksafe.py + - ddtrace/internal/gitmetadata.py + - ddtrace/internal/glob_matching.py + - ddtrace/internal/logger.py + - ddtrace/internal/hostname.py + - ddtrace/internal/http.py + - ddtrace/internal/injection.py + - ddtrace/internal/logger.py + - ddtrace/internal/metrics.py + - ddtrace/internal/module.py + - ddtrace/internal/packages.py + - ddtrace/internal/third-party.tar.gz + - ddtrace/internal/periodic.py + - ddtrace/internal/rate_limiter.py + - ddtrace/internal/safety.py + - ddtrace/internal/service.py + - ddtrace/internal/uds.py + - ddtrace/internal/utils/* + - ddtrace/internal/uwsgi.py + - ddtrace/internal/wrapping/* + - ddtrace/__init__.py + - ddtrace/py.typed + - ddtrace/version.py + - ddtrace/settings/config.py + - src/core/* + datastreams: + - ddtrace/internal/datastreams/* + - ddtrace/data_streams.py + - ddtrace/ext/schema.py + git: + - ddtrace/ext/git.py + opentelemetry: + - ddtrace/opentelemetry/* + - ddtrace/internal/opentelemetry/* + opentracer: + - ddtrace/opentracer/* + profiling: + - ddtrace/profiling/* + - ddtrace/internal/datadog/profiling/* + - ddtrace/internal/processor/endpoint_call_counter.py + - ddtrace/settings/profiling.py + remoteconfig: + - ddtrace/internal/remoteconfig/* + runtime: + - ddtrace/runtime/* + serverless: + - ddtrace/internal/serverless/* + settings: + - ddtrace/settings/* + sourcecode: + - ddtrace/sourcecode/* + symbol_db: + - ddtrace/internal/symbol_db/* + - ddtrace/settings/symbol_db.py + telemetry: + - ddtrace/internal/telemetry/* + tracing: + - ddtrace/_hooks.py + - ddtrace/_logger.py + - ddtrace/_monkey.py + - ddtrace/_trace/* + - ddtrace/trace/* + - ddtrace/constants.py + - ddtrace/context.py + - ddtrace/filters.py + - ddtrace/pin.py + - ddtrace/provider.py + - ddtrace/sampler.py + - ddtrace/sampling_rule.py + - ddtrace/span.py + - ddtrace/tracer.py + - ddtrace/tracing/* + - ddtrace/settings/__init__.py + - ddtrace/settings/config.py + - ddtrace/settings/http.py + - ddtrace/settings/exceptions.py + - ddtrace/settings/integration.py + - ddtrace/internal/_encoding.py* + - ddtrace/internal/_tagset.py* + - ddtrace/internal/_utils.* + - ddtrace/internal/constants.py + - ddtrace/internal/encoding.py + - ddtrace/internal/flare/* + - ddtrace/internal/pack.h + - ddtrace/internal/pack_template.h + - ddtrace/internal/peer_service/* + - ddtrace/settings/peer_service.py + - ddtrace/internal/processor/__init__.py + - ddtrace/internal/processor/stats.py + - ddtrace/internal/runtime/* + - ddtrace/internal/sampling.py + - ddtrace/internal/schema/* + - ddtrace/internal/sma.py + - ddtrace/internal/tracemethods.py + - ddtrace/internal/sysdep.h + - ddtrace/internal/writer/* + vendor: + - ddtrace/vendor/* +suites: + ddtracerun: + parallelism: 6 + paths: + - '@contrib' + - '@bootstrap' + - '@core' + - tests/commands/* + - tests/ddtrace_run.py + runner: riot + services: + - redis + integration_agent: + paths: + - '@tracing' + - '@bootstrap' + - '@core' + - '@contrib' + - tests/integration/* + - tests/snapshots/tests.integration.* + pattern: integration-latest* + runner: riot + integration_testagent: + paths: + - '@tracing' + - '@bootstrap' + - '@core' + - '@contrib' + - tests/integration/* + - tests/snapshots/tests.integration.* + pattern: integration-snapshot* + runner: riot + snapshot: true + internal: + paths: + - '@core' + - '@remoteconfig' + - '@symbol_db' + - '@tracing' + - ddtrace/internal/* + - tests/internal/* + - tests/submod/* + - tests/cache/* + runner: riot + snapshot: true + profile: + env: + DD_TRACE_AGENT_URL: '' + parallelism: 20 + paths: + - '@bootstrap' + - '@core' + - '@profiling' + - tests/profiling/* + - tests/profiling_v2/* + pattern: profile$|profile-v2 + retry: 2 + runner: riot + runtime: + paths: + - '@bootstrap' + - '@core' + - '@runtime' + - tests/runtime/* + runner: riot + skip: true + telemetry: + parallelism: 6 + paths: + - '@bootstrap' + - '@contrib' + - '@core' + - '@telemetry' + - '@tracing' + - '@settings' + - '@profiling' + - tests/telemetry/* + - tests/snapshots/tests.telemetry.* + runner: riot + snapshot: true + tracer: + env: + DD_TRACE_AGENT_URL: http://localhost:8126 + parallelism: 9 + paths: + - '@tracing' + - '@bootstrap' + - '@core' + - '@contrib' + - '@llmobs' + - '@serverless' + - '@remoteconfig' + - '@futures' + - '@ci_visibility' + - tests/tracer/* + - tests/snapshots/test_* + retry: 2 + runner: riot + vendor: + paths: + - '@vendor' + - tests/vendor/* + runner: riot From b7ca2d6f57442fb8f3599273df4cc6ec3d346c38 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:59:51 -0500 Subject: [PATCH 236/372] chore: update changelog for version 2.13 - 2.17 patch releases (#11552) - [x] update changelog for version 2.13.3, 2.14.5, 2.14.7, 2.15.3, 2.15.4, 2.16.1, 2.16.3, 2.16.4, 2.17.0, 2.17.1, 2.17.2 --- CHANGELOG.md | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e52b18d1ee6..8d259f8624a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,261 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.17.2 + +### Bug Fixes + +- ASM + - Ensures that common patches for exploit prevention and SCA are only loaded if required, and only loaded once. + +- LLM Observability + - Ensures bedrock spans are finished even when streamed responses are not fully consumed. + - Fixes an issue where decorators were not tracing generator functions properly. + +- Tracing + - `botocore`: Resolves an issue in the Bedrock integration where not consuming the full response stream would prevent spans from finishing. + - `celery`: Changes celery `out.host` span tag to point towards broker host url instead of local celery process hostname. Fixes inferred service representation issues when using celery. + - `grpcaio`: Resolves a concurrency bug where distributed tracing headers were overwritten resulting in spans being assigned to the wrong trace. + + +--- + +## 2.17.1 + +### Bug Fixes +- ASM + - Resolves an issue where some root spans were not appropriately tagged for ASM standalone. +- Code Security + - Patches the module dir function so original pre-patch results are not changed. +- Tracing + - Resolves an issue where the default versions of `click` and `jinja2` installed on 3.8 were outside of the allowed minimum versions for autoinstrumentation. + + +--- + +## 2.17.0 + +### New Features +- ASM + - Support added for session fingerprints. + +- LLM Observability + - When not using a provider integration (OpenAI, Anthropic, or Bedrock) with the LangChain integration, token metrics will be appended to the LLM Observability `llm` span. + - LLM Observability: When langchain's `chat_model.with_structured_output(..., method="json_mode")` is used, or `response_format={"type": "json_object"}` is passed into a langchain chat model invocation, the LLM Observability span will be an `llm` span instead of a `workflow` span. + +- SSI + - Adds `requirements.json` to SSI artifact for bailing out on unsupported systems. + +- Tracing + - Adds support for expanding AWS request/response Payloads into flattened span tags. + - Updates the service naming algorithm to infer the base service name when `DD_SERVICE` is not set, replacing instances of `'unnamed-python-service'`. Ensures that a more meaningful service name is used whenever possible, enhancing clarity in service identification. + +### Bug Fixes +- ASM + - The new user events policy is preventing users PII to be added by default as span tags. To allow customers using the Django auto instrumentation to still have those information, new environment variables have been added. In particular `DD_DJANGO_INCLUDE_EMAIL` (false by default), will tag user events with user email as before. + +- Code Security/IAST + - Adds `umap`, `numba` and `pynndescent` to the Code Security denylist. + - Adds `googlecloudsdk` and `google auth` to the Code Security deny list. + - Resolves an issue where importing the `google.cloud.storage.batch` module would fail raising an ImportError + +- Crashtracking + - Fixes an issue where the use of the Crashtracking component could result in zombie processes. + +- Lib-Injection + - Supports Python 2.7+ for injection compatibility check. + - Adds more commands to the auto-injection denylist. + - Ensures we do not import the user installed `ddtrace` if it is present. + - Fixes injection guardrail check when `sys.argv` is not available. + +- LLM Observability + - Resolves an issue where annotating spans with non-ASCII language input/output values resulted in encoded unicode being submitted. + +- Profiling + - Fixes a data race where span information associated with a thread was read and updated concurrently, leading to segfaults + - Fixes an issue where cpu-time was not profiled for services using gunicorn, when `DD_PROFILING_STACK_V2_ENABLED` was set. + - Fixes an issue where enabling native exporter via `DD_PROFILING_EXPORT_LIBDD_ENABLED`, `DD_PROFILING_TIMELINE_ENABLED` or `DD_PROFILING_STACK_V2_ENABLED` turned off live heap profiling. + - The lock profiler would log a warning if it couldn't determine a name for a lock, and it would try determining a name multiple times for the same lock. This lead to excessive log spam. Downgrade this to a debug log and only try to determine the name once. + - Fixes an issue where the profiler was allocating too much memory from `ensure_binary_or_empty()` function, on Python versions before 3.12, with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + - Fixes an issue where the sample pool could deadlock after `fork()` by clearing it in the child process. + - When a Python thread finishes, this change frees memory used for mapping its thread id to `Span`. The mapping is populated and used when `DD_PROFILING_ENDPOINT_COLLECTION_ENABLED` and `DD_PROFILING_STACK_V2_ENABLED` were set to enable grouping of profiles for endpoints. + +- Tracing + - Updates the inferred base service name algorithm to ensure that arguments following `--ddtrace` are no longer skipped when executing tests with pytest. Previously, the algorithm misinterpreted these arguments as standard flags, overlooking possible test paths that may contribute to the inferred service name. + - `botocore`: Resolves the issue where the span pointer for deserialized DynamoDB requests (through the resource-based API) were not being generated. + - `botocore`: Resolves an issue where our span pointer calculation code added recently logged unactionable messages. + - `pymongo`: add type checking to solve an issue where `NoneType` instead of expected `Pin` object would throw an error in `TracedTopology` method. + + +--- + +## 2.16.5 + +### Bug Fixes + +- ASM + - Ensures that common patches for exploit prevention and sca are only loaded if required, and only loaded once. + - Resolves an issue where some root span where not appropriately tagged for ASM standalone. + +- Auto-Instrumentation + - Resolves an issue where the default versions of `click` and `jinja2` installed on python3.8 were outside of the allowed minimum versions for auto-instrumentation. + +- Code Security + - Patches the module dir function so original pre-patch results are not changed. + +- LLM Observability + - Ensures bedrock spans are finished even when streamed responses are not fully consumed. + +- Tracing + - `botocore`: Resolves an issue in the Bedrock integration where not consuming the full response stream would prevent spans from finishing. + + +--- + +## 2.16.3 + +### Bug Fixes + + - Code Security: add umap, numba and pynndescent to the Code Security denylist. + + +--- + +## 2.16.1 + +### Bug Fixes + +- Threats + - The new user events policy is preventing users PII to be added by default as span tags. To allow customers using the Django auto instrumentation to still have those information, new environment variables have been added. In particular `DD_DJANGO_INCLUDE_EMAIL` (false by default), will tag user events with user email as before. + +- Code Security + - Add googlecloudsdk and google auth to the Code Security deny list. + +- Crashtracking + - Fixes an issue where the use of the crashtracking component could result in zombie processes. + +- Lib-Injection + - This fix adds more commands to the auto-injection denylist. + - This fix ensures we do not import the user installed `ddtrace` if it is present. + +- LLM Observability + - Resolves an issue where annotating spans with non-ASCII language input/output values resulted in encoded unicode being submitted. + +- Profiling + - Fixes an issue where cpu-time was not profiled for services using gunicorn, when `DD_PROFILING_STACK_V2_ENABLED` was set. + - Fixes an issue where the profiler was allocating too much memory from `ensure_binary_or_empty()` function, on Python versions before 3.12, with `DD_PROFILING_EXPORT_LIBDD_ENABLED` or `DD_PROFILING_TIMELINE_ENABLED`. + - Fixes an issue where the sample pool could deadlock after `fork()` by clearing it in the child process. + + +--- + +## 2.15.4 + +### Bug Fixes + +- ASM + - Ensures that common patches for exploit prevention and sca are only loaded if required, and only loaded once. + - Resolves an issue where some root span where not appropriately tagged for ASM standalone. + +- Auto-Instrumentation + - Resolves an issue where the default versions of `click` and `jinja2` installed on python3.8 were outside of the allowed minimum versions for auto-instrumentation. + +- Code Security + - Patches the module dir function so original pre-patch results are not changed. + +- LLM Observability + - Ensures bedrock spans are finished even when streamed responses are not fully consumed. + +- Tracing + - `botocore`: Resolves an issue in the Bedrock integration where not consuming the full response stream would prevent spans from finishing. + + +--- + +## 2.15.3 + +### Bug Fixes + +- ASM: + - The new user events policy is preventing users PII to be added by default as span tags. To allow customers using the Django auto instrumentation to still have those information, new environment variables have been added. In particular DD\_DJANGO\_INCLUDE\_EMAIL (false by default), will tag user events with user email as before. + +- LLM Observability: + - Resolves an issue where annotating spans with non-ASCII language input/output values resulted in encoded unicode being submitted. + +- Code Security: + - Add googlecloudsdk,google auth, umap, numba and pynndescent to the Code Security deny list. + +- Profiling: + - Fixes an issue where cpu-time was not profiled for services using gunicorn, when \`DD\_PROFILING\_STACK\_V2\_ENABLED was set. + + - The lock profiler would log a warning if it couldn't determine a + name for a lock, and it would try determining a name multiple times for the same lock. This lead to excessive log spam. Downgrade this to a debug log and only try to determine the name once. + + - Fixes an issue where the sample pool could deadlock after `fork()` + by clearing it in the child process. + + +--- + +## 2.14.7 + +### Bug Fixes + +- Code Security: + - Add googlecloudsdk and google auth to the Code Security deny list. + +- Profiling: + - Fixes an issue where cpu-time was not profiled for services using gunicorn, when `DD_PROFILING_STACK_V2_ENABLED` was set. + + - Fixes an issue where the sample pool could deadlock after `fork()` by clearing it in the child process. + + +--- + +## 2.14.5 + +### Bug Fixes + + - LLM Observability: This fix resolves an issue where LLMObs.enable() did not patch google\_generativeai library. + - CI Visibility: fixes a bug where `CODEOWNERS` would incorrectly fail to discard line-level trailing comments (eg: `@code/owner # my comment` would result in codeowners being parsed as `@code/owner`, `#`, `my`, and `comment`) + - CI Visibility: fixes unnecessary logging of an exception that would appear when trying to upload git metadata in an environment without functioning git (eg: missing `git` binary or `.git` directory) + - elasticsearch: this fix resolves an issue where span tags were not fully populated on "sampled" spans, causing metric dimensions to be incorrect when spans were prematurely marked as sampled, including resource\_name. + - Code security: This fix resolves an issue where partial matches on function names we aimed to patch were being patched instead of full matches on them. + - Code Security: This fix resolves an issue where importing the `google.cloud.storage.batch` module would fail raising an ImportError + - profiling: Improves the error message when the native exporter fails to load and stops profiling from starting if ddtrace is also being injected. + - profiling: fix a data race where span information associated with a thread was read and updated concurrently, leading to segfaults + - profiling: resolves an issue where endpoint profiling for stack v2 throws `TypeError` exception when it is given a `Span` with `None` span\_type. + + +--- + +## 2.13.3 + +### Bug Fixes + +- CI Visibility + - Fixes a bug where `CODEOWNERS` would incorrectly fail to discard line-level trailing comments (eg: `@code/owner # my comment` would result in codeowners being parsed as `@code/owner`, `#`, `my`, and `comment`) + - Fixes unnecessary logging of an exception that would appear when trying to upload git metadata in an environment without functioning git (eg: missing `git` binary or `.git` directory) + +- Code security + - Resolves an issue where partial matches on function names we aimed to patch were being patched instead of full matches on them. + - Resolves an issue where importing the `google.cloud.storage.batch` module would fail raising an ImportError + +- LLM Observability + - Resolves an issue where LLM Observability evaluation metrics were not being submitted in forked processes. The evaluation metric writer thread now automatically restarts when a forked process is detected. + - Resolves an issue where input and output values equal to zero were not being annotated on workflow, task, agent and tool spans when using `LLMObs.annotate`. + +- Profiling + - Improves the error message when the native exporter fails to load and stops profiling from starting if ddtrace is also being injected. + - Fixes a data race where span information associated with a thread was read and updated concurrently, leading to segfaults + - Resolves an issue where endpoint profiling for stack v2 throws `TypeError` exception when it is given a `Span` with `None` span_type. + +- Tracing + - `elasticsearch`: Resolves an issue where span tags were not fully populated on "sampled" spans, causing metric dimensions to be incorrect when spans were prematurely marked as sampled, including resource_name. + + --- ## 2.16.4 From f3ab1333d282adac5ade2e1eb40ec5e171a59bdd Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Wed, 27 Nov 2024 13:52:16 -0500 Subject: [PATCH 237/372] chore: improve dynamodb pointer config discoverability (#11566) Unfortunately the config is pretty far away from where we actually use it (since we don't always need to use it). This creates an implicit link between the configuration and the error messaging. But the alternative (feeding the config language down through the stacks, either as a separate parameter or as bundled object for the `dynamodb_primary_key_names_for_tables` variable) seems a bit too heavy-handed unless we absolutely need to do it that way. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py index 2aa140a0556..9b7025a9fac 100644 --- a/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py +++ b/ddtrace/_trace/utils_botocore/span_pointers/dynamodb.py @@ -234,7 +234,9 @@ def _extract_primary_key_names_from_configuration( return dynamodb_primary_key_names_for_tables[table_name] except KeyError as e: log.warning( - "span pointers: failed to extract %s span pointer: table %s not found in primary key names", + "span pointers: failed to extract %s span pointer: table %s not found in primary key names; " + "Please set them through ddtrace.config.botocore['dynamodb_primary_key_names_for_tables'] or " + "DD_BOTOCORE_DYNAMODB_TABLE_PRIMARY_KEYS", operation, e, ) From 8eb0b443203ea470fb1e482497918bb578c059e1 Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Wed, 27 Nov 2024 13:01:45 -0800 Subject: [PATCH 238/372] chore: add support for max items in baggage (#11421) Fixing baggage max items implementation based off the change in the RFC. "If baggage data exceeds one or both of these limits, APM SDKs should drop baggage name/value pairs until both conditions are met. For example, if baggage contains 70 name/value pairs, the SDK should only add 64 of them to the baggage header and drop the other 6. The W3C leaves the process of selecting which pairs to keep or drop up to implementers. The simplest algorithm in this example is to keep the first 64 pairs and drop the last 6." Also updating the test that checks max baggage items. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/propagation/http.py | 31 ++++++++++++---------- tests/tracer/test_propagation.py | 45 ++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 343f653f548..a1664664ace 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -1,3 +1,4 @@ +import itertools import re import sys from typing import Any # noqa:F401 @@ -912,21 +913,23 @@ def _inject(span_context: Context, headers: Dict[str, str]) -> None: if not baggage_items: return - if len(baggage_items) > DD_TRACE_BAGGAGE_MAX_ITEMS: - log.warning("Baggage item limit exceeded") - return - try: - header_value = ",".join( - f"{_BaggageHeader._encode_key(key)}={_BaggageHeader._encode_value(value)}" - for key, value in baggage_items - ) - - buf = bytes(header_value, "utf-8") - if len(buf) > DD_TRACE_BAGGAGE_MAX_BYTES: - log.warning("Baggage header size exceeded") - return - + if len(baggage_items) > DD_TRACE_BAGGAGE_MAX_ITEMS: + log.warning("Baggage item limit exceeded, dropping excess items") + baggage_items = itertools.islice(baggage_items, DD_TRACE_BAGGAGE_MAX_ITEMS) # type: ignore + + encoded_items: List[str] = [] + total_size = 0 + for key, value in baggage_items: + item = f"{_BaggageHeader._encode_key(key)}={_BaggageHeader._encode_value(value)}" + item_size = len(item.encode("utf-8")) + (1 if encoded_items else 0) # +1 for comma if not first item + if total_size + item_size > DD_TRACE_BAGGAGE_MAX_BYTES: + log.warning("Baggage header size exceeded, dropping excess items") + break # stop adding items when size limit is reached + encoded_items.append(item) + total_size += item_size + + header_value = ",".join(encoded_items) headers[_HTTP_HEADER_BAGGAGE] = header_value except Exception: diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index c97d2ce6b3d..2e1a299c4d4 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -38,6 +38,7 @@ from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID from ddtrace.propagation.http import HTTPPropagator +from ddtrace.propagation.http import _BaggageHeader from ddtrace.propagation.http import _TraceContext from tests.contrib.fastapi.conftest import client as fastapi_client # noqa:F401 from tests.contrib.fastapi.conftest import fastapi_application # noqa:F401 @@ -3163,16 +3164,15 @@ def test_llmobs_parent_id_not_injected_by_default(): ], ) def test_baggageheader_inject(span_context, expected_headers): - from ddtrace.propagation.http import _BaggageHeader - headers = {} _BaggageHeader._inject(span_context, headers) assert headers == expected_headers def test_baggageheader_maxitems_inject(): + import urllib.parse + from ddtrace.internal.constants import DD_TRACE_BAGGAGE_MAX_ITEMS - from ddtrace.propagation.http import _BaggageHeader headers = {} baggage_items = {} @@ -3180,18 +3180,45 @@ def test_baggageheader_maxitems_inject(): baggage_items[f"key{i}"] = f"val{i}" span_context = Context(baggage=baggage_items) _BaggageHeader._inject(span_context, headers) - assert "baggage" not in headers + assert "baggage" in headers + header_value = headers["baggage"] + items = header_value.split(",") + assert len(items) == DD_TRACE_BAGGAGE_MAX_ITEMS + + expected_keys = [f"key{i}" for i in range(DD_TRACE_BAGGAGE_MAX_ITEMS)] + for item in items: + key, value = item.split("=", 1) + key = urllib.parse.unquote(key) + assert key in expected_keys def test_baggageheader_maxbytes_inject(): from ddtrace.internal.constants import DD_TRACE_BAGGAGE_MAX_BYTES - from ddtrace.propagation.http import _BaggageHeader headers = {} - baggage_items = {"foo": ("a" * DD_TRACE_BAGGAGE_MAX_BYTES)} + # baggage item that exceeds the maximum byte size + baggage_items = {"foo": "a" * (DD_TRACE_BAGGAGE_MAX_BYTES + 1)} + span_context = Context(baggage=baggage_items) + _BaggageHeader._inject(span_context, headers) + # since the baggage item exceeds the max bytes, no header should be injected + header_value = headers["baggage"] + assert header_value == "" + + # multiple baggage items to test dropping items when the total size exceeds the limit + headers = {} + baggage_items = { + "key1": "a" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), + "key2": "b" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), + "key3": "c" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), + "key4": "d", + } span_context = Context(baggage=baggage_items) _BaggageHeader._inject(span_context, headers) - assert "baggage" not in headers + header_value = headers["baggage"] + header_size = len(header_value.encode("utf-8")) + assert header_size <= DD_TRACE_BAGGAGE_MAX_BYTES + assert "key4" not in header_value + assert "key2" in header_value @pytest.mark.parametrize( @@ -3217,8 +3244,6 @@ def test_baggageheader_maxbytes_inject(): ], ) def test_baggageheader_extract(headers, expected_baggage): - from ddtrace.propagation.http import _BaggageHeader - context = _BaggageHeader._extract(headers) assert context._baggage == expected_baggage @@ -3239,8 +3264,6 @@ def test_baggageheader_extract(headers, expected_baggage): ], ) def test_baggage_malformedheader_extract(headers, expected_baggage): - from ddtrace.propagation.http import _BaggageHeader - context = _BaggageHeader._extract(headers) assert context._baggage == expected_baggage From 21f43e55cd0689fc48d3f0e0816d70530ba88a91 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 27 Nov 2024 16:02:11 -0500 Subject: [PATCH 239/372] chore(telemetry): fixes namespace for span pointer metric (#11565) This metric is currently generating invalid payloads and is being dropped by the tracer-telemetry-intake service. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Rachel Yang --- ddtrace/_trace/telemetry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/_trace/telemetry.py b/ddtrace/_trace/telemetry.py index 4611cedba51..f9cd9ef79b9 100644 --- a/ddtrace/_trace/telemetry.py +++ b/ddtrace/_trace/telemetry.py @@ -6,7 +6,7 @@ def record_span_pointer_calculation(context: str, span_pointer_count: int) -> None: telemetry_writer.add_count_metric( - namespace="tracer", + namespace="tracers", name="span_pointer_calculation", value=1, tags=(("context", context), ("count", _span_pointer_count_to_tag(span_pointer_count))), @@ -45,7 +45,7 @@ def record_span_pointer_calculation_issue( tags += additional_tags telemetry_writer.add_count_metric( - namespace="tracer", + namespace="tracers", name="span_pointer_calculation.issue", value=1, tags=tags, From 91ac84a45f99e9d50a0be874cfc4f034ba8e532e Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:13:57 +0100 Subject: [PATCH 240/372] chore(asm): add env var to control stack trace report depth crop (#11546) This is PR 1 for adding stack trace report to IAST. This PR focus on improving support for configuration of stack trace report. - add `DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT` to control stack trace reports - (silently) add `DD_IAST_STACK_TRACE_ENABLED` to enable stack trace report in IAST. This will be follow by other PRs to add the feature to IAST - update related tests - change ownership of telemetry tests due to the fact that any change in env var needs to be reflected in those tests. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/CODEOWNERS | 1 + .gitlab/benchmarks.yml | 1 + ddtrace/appsec/_constants.py | 8 +++++ .../_exploit_prevention/stack_traces.py | 9 ++---- ddtrace/settings/asm.py | 29 ++++++++++++------- docs/configuration.rst | 28 ++++++++++++++++++ ...ce_depth_top_percent-a840b8487674c046.yaml | 11 +++++++ tests/telemetry/test_writer.py | 2 ++ 8 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/max_stack_trace_depth_top_percent-a840b8487674c046.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 771e1a147ae..4d12f8aeb93 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,7 @@ riotfile.py @DataDog/apm-python .riot/requirements/ @DataDog/apm-python CHANGELOG.md @DataDog/apm-python README.md @DataDog/apm-python +tests/telemetry @DataDog/apm-python # Guild setup.py @DataDog/python-guild diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 9d56afcdf09..6f0de408e83 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -84,6 +84,7 @@ benchmark-serverless: tags: ["arch:amd64"] when: on_success needs: [ "benchmark-serverless-trigger" ] + allow_failure: true script: - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ddbuild.io/DataDog/serverless-tools.git ./serverless-tools && cd ./serverless-tools - ./ci/check_trigger_status.sh diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 7fefbd6880b..a127ebb6615 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -138,6 +138,11 @@ class IAST(metaclass=Constant_Class): DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES" SEP_MODULES: Literal[","] = "," PATCH_ADDED_SYMBOL_PREFIX: Literal["_ddtrace_"] = "_ddtrace_" + REDACTION_ENABLED: Literal["DD_IAST_REDACTION_ENABLED"] = "DD_IAST_REDACTION_ENABLED" + REDACTION_NAME_PATTERN: Literal["DD_IAST_REDACTION_NAME_PATTERN"] = "DD_IAST_REDACTION_NAME_PATTERN" + REDACTION_VALUE_PATTERN: Literal["DD_IAST_REDACTION_VALUE_PATTERN"] = "DD_IAST_REDACTION_VALUE_PATTERN" + REDACTION_VALUE_NUMERAL: Literal["DD_IAST_REDACTION_VALUE_NUMERAL"] = "DD_IAST_REDACTION_VALUE_NUMERAL" + STACK_TRACE_ENABLED: Literal["DD_IAST_STACK_TRACE_ENABLED"] = "DD_IAST_STACK_TRACE_ENABLED" METRICS_REPORT_LVLS = ( (TELEMETRY_DEBUG_VERBOSITY, TELEMETRY_DEBUG_NAME), @@ -325,6 +330,9 @@ class EXPLOIT_PREVENTION(metaclass=Constant_Class): STACK_TRACE_ENABLED: Literal["DD_APPSEC_STACK_TRACE_ENABLED"] = "DD_APPSEC_STACK_TRACE_ENABLED" MAX_STACK_TRACES: Literal["DD_APPSEC_MAX_STACK_TRACES"] = "DD_APPSEC_MAX_STACK_TRACES" MAX_STACK_TRACE_DEPTH: Literal["DD_APPSEC_MAX_STACK_TRACE_DEPTH"] = "DD_APPSEC_MAX_STACK_TRACE_DEPTH" + STACK_TOP_PERCENT: Literal[ + "DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT" + ] = "DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT" class TYPE(metaclass=Constant_Class): CMDI: Literal["command_injection"] = "command_injection" diff --git a/ddtrace/appsec/_exploit_prevention/stack_traces.py b/ddtrace/appsec/_exploit_prevention/stack_traces.py index b15b7adb782..8276d0c51bc 100644 --- a/ddtrace/appsec/_exploit_prevention/stack_traces.py +++ b/ddtrace/appsec/_exploit_prevention/stack_traces.py @@ -12,11 +12,6 @@ import ddtrace.tracer -# remove_top default value of 8 -# report_stack/_waf_action/waf_callable/call_waf_callback -# wrapped_open/rasp/_wrap_call/patch_func - - def report_stack( message: str, span: Optional[Span] = None, crop_stack: Optional[str] = None, stack_id: Optional[str] = None ): @@ -50,8 +45,8 @@ def report_stack( "id": stack_id, "message": message, } - if asm_config._ep_max_stack_trace_depth and len(stack) > asm_config._ep_max_stack_trace_depth: - top_stack = asm_config._ep_max_stack_trace_depth // 4 + if len(stack) > asm_config._ep_max_stack_trace_depth > 0: + top_stack = int(asm_config._ep_max_stack_trace_depth * asm_config._ep_stack_top_percent / 100) bottom_stack = asm_config._ep_max_stack_trace_depth - top_stack iterator: Iterable[int] = chain(range(top_stack), range(len(stack) - bottom_stack, len(stack))) else: diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index f508bf10eb9..3ec15ae67ef 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -21,16 +21,16 @@ from ddtrace.vendor.debtcollector import deprecate -def _validate_sample_rate(r: float) -> None: - if r < 0.0 or r > 1.0: - raise ValueError("sample rate value must be between 0.0 and 1.0") - - def _validate_non_negative_int(r: int) -> None: if r < 0: raise ValueError("value must be non negative") +def _validate_percentage(r: float) -> None: + if r < 0 or r > 100: + raise ValueError("percentage value must be between 0 and 100") + + def _parse_options(options: List[str]): def parse(str_in: str) -> str: for o in options: @@ -116,7 +116,6 @@ class ASMConfig(Env): _user_model_email_field = Env.var(str, APPSEC.USER_MODEL_EMAIL_FIELD, default="") _user_model_name_field = Env.var(str, APPSEC.USER_MODEL_NAME_FIELD, default="") _api_security_enabled = Env.var(bool, API_SECURITY.ENV_VAR_ENABLED, default=True) - _api_security_sample_rate = 0.0 _api_security_sample_delay = Env.var(float, API_SECURITY.SAMPLE_DELAY, default=30.0) _api_security_parse_response_body = Env.var(bool, API_SECURITY.PARSE_RESPONSE_BODY, default=True) @@ -141,10 +140,10 @@ class ASMConfig(Env): str, APPSEC.OBFUSCATION_PARAMETER_VALUE_REGEXP, default=DEFAULT.APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP ) - _iast_redaction_enabled = Env.var(bool, "DD_IAST_REDACTION_ENABLED", default=True) + _iast_redaction_enabled = Env.var(bool, IAST.REDACTION_ENABLED, default=True) _iast_redaction_name_pattern = Env.var( str, - "DD_IAST_REDACTION_NAME_PATTERN", + IAST.REDACTION_NAME_PATTERN, default=r"(?i)^.*(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|" + r"public_?|access_?|secret_?)key(?:_?id)?|password|token|username|user_id|last.name|" + r"consumer_?(?:id|key|secret)|" @@ -152,7 +151,7 @@ class ASMConfig(Env): ) _iast_redaction_value_pattern = Env.var( str, - "DD_IAST_REDACTION_VALUE_PATTERN", + IAST.REDACTION_VALUE_PATTERN, default=r"(?i)bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|password|gh[opsu]_[0-9a-zA-Z]{36}|" + r"ey[I-L][\w=-]+\.ey[I-L][\w=-]+(\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY" + r"[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}", @@ -172,6 +171,13 @@ class ASMConfig(Env): int, EXPLOIT_PREVENTION.MAX_STACK_TRACE_DEPTH, default=32, validator=_validate_non_negative_int ) + # percentage of stack trace reported on top, in case depth is larger than max_stack_trace_depth + _ep_stack_top_percent = Env.var( + float, EXPLOIT_PREVENTION.STACK_TOP_PERCENT, default=75.0, validator=_validate_percentage + ) + + _iast_stack_trace_enabled = Env.var(bool, IAST.STACK_TRACE_ENABLED, default=True) + # Django ATO _django_include_user_name = Env.var(bool, "DD_DJANGO_INCLUDE_USER_NAME", default=True) _django_include_user_email = Env.var(bool, "DD_DJANGO_INCLUDE_USER_EMAIL", default=False) @@ -201,7 +207,6 @@ class ASMConfig(Env): "_user_model_email_field", "_user_model_name_field", "_api_security_enabled", - "_api_security_sample_rate", "_api_security_sample_delay", "_api_security_parse_response_body", "_waf_timeout", @@ -212,6 +217,8 @@ class ASMConfig(Env): "_ep_stack_trace_enabled", "_ep_max_stack_traces", "_ep_max_stack_trace_depth", + "_ep_stack_top_percent", + "_iast_stack_trace_enabled", "_asm_config_keys", "_deduplication_enabled", "_django_include_user_name", @@ -221,7 +228,7 @@ class ASMConfig(Env): ] _iast_redaction_numeral_pattern = Env.var( str, - "DD_IAST_REDACTION_VALUE_NUMERAL", + IAST.REDACTION_VALUE_NUMERAL, default=r"^[+-]?((0b[01]+)|(0x[0-9A-Fa-f]+)|(\d+\.?\d*(?:[Ee][+-]?\d+)?|\.\d+(?:[Ee][+-]" + r"?\d+)?)|(X\'[0-9A-Fa-f]+\')|(B\'[01]+\'))$", ) diff --git a/docs/configuration.rst b/docs/configuration.rst index 9fb0cb0a7ef..1971e092d79 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -438,6 +438,29 @@ AppSec default: None description: Whether to enable/disable SCA (Software Composition Analysis). + DD_APPSEC_MAX_STACK_TRACES: + type: Integer + default: 2 + description: Maximum number of stack traces reported for each trace. + + DD_APPSEC_MAX_STACK_TRACE_DEPTH: + type: Integer + default: 32 + description: Maximum number of frames in a stack trace report. 0 means no limit. + + DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT: + type: Integer + default: 75 + description: | + Percentage of reported stack trace frames to be taken from the top of the stack in case of a stack trace truncation. + For example, if DD_APPSEC_MAX_STACK_TRACE_DEPTH is set to 25 and DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT is set to 60, + if a stack trace has more than 25 frames, the top 15 (25*0.6=15)frames and the bottom 10 frames will be reported. + + DD_APPSEC_STACK_TRACE_ENABLED: + type: Boolean + default: True + description: Whether to enable stack traces in reports for ASM. Currently used for exploit prevention reports. + DD_IAST_ENABLED: type: Boolean default: False @@ -485,6 +508,11 @@ AppSec version_added: v1.17.0: + DD_IAST_STACK_TRACE_ENABLED: + type: Boolean + default: True + description: Whether to enable stack traces in reports for Code Security/IAST. + DD_IAST_VULNERABILITIES_PER_REQUEST: type: Integer default: 2 diff --git a/releasenotes/notes/max_stack_trace_depth_top_percent-a840b8487674c046.yaml b/releasenotes/notes/max_stack_trace_depth_top_percent-a840b8487674c046.yaml new file mode 100644 index 00000000000..7670dba1a7c --- /dev/null +++ b/releasenotes/notes/max_stack_trace_depth_top_percent-a840b8487674c046.yaml @@ -0,0 +1,11 @@ +--- +upgrade: + - | + ASM: With this upgrade, you can now control how the stack trace report are cropped when reported for exploit prevention or IAST. + - DD_APPSEC_MAX_STACK_TRACE_DEPTH allowed to control the maximum stack trace size reported (default 32) + - DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT allows now to specify how the stack trace is cropped as a percentage. + + For example, a value of 100 will report the top DD_APPSEC_MAX_STACK_TRACE_DEPTH frames from the stack, + while a value of 0 will report the bottom DD_APPSEC_MAX_STACK_TRACE_DEPTH frames of the trace. + A value of 50 will report half of DD_APPSEC_MAX_STACK_TRACE_DEPTH (rounded down) frames from the top of the stack and the rest from bottom. + Default value is 75. diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 365bfb0cdc0..7718710ff60 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -295,6 +295,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_APPSEC_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_APPSEC_MAX_STACK_TRACES", "origin": "default", "value": 2}, {"name": "DD_APPSEC_MAX_STACK_TRACE_DEPTH", "origin": "default", "value": 32}, + {"name": "DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT", "origin": "default", "value": 75.0}, { "name": "DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP", "origin": "default", @@ -377,6 +378,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python "[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}", }, {"name": "DD_IAST_REQUEST_SAMPLING", "origin": "default", "value": 30.0}, + {"name": "DD_IAST_STACK_TRACE_ENABLED", "origin": "default", "value": True}, {"name": "DD_IAST_TELEMETRY_VERBOSITY", "origin": "default", "value": "INFORMATION"}, {"name": "DD_INJECT_FORCE", "origin": "env_var", "value": True}, {"name": "DD_INSTRUMENTATION_INSTALL_ID", "origin": "default", "value": None}, From 5aa3596756dec6f9a1a3b93d6e9022ae97ed0c05 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:01:45 +0100 Subject: [PATCH 241/372] test(asm): mark test_cmdi as flaky (#11572) mark test_cmdi as flaky. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/integrations/pygoat_tests/test_pygoat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/appsec/integrations/pygoat_tests/test_pygoat.py b/tests/appsec/integrations/pygoat_tests/test_pygoat.py index 2be426cd37e..e60d5336b35 100644 --- a/tests/appsec/integrations/pygoat_tests/test_pygoat.py +++ b/tests/appsec/integrations/pygoat_tests/test_pygoat.py @@ -123,6 +123,7 @@ def test_weak_hash(client): assert vulnerability_in_traces("WEAK_HASH", client.agent_session) +@flaky(1735812000) def test_cmdi(client): payload = {"domain": "google.com && ls", "csrfmiddlewaretoken": client.csrftoken} reply = client.pygoat_session.post(PYGOAT_URL + "/cmd_lab", data=payload, headers=TESTAGENT_HEADERS) From 17631cfff98996da21a423d9229c13ee3aeb40fe Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 28 Nov 2024 19:00:23 +0100 Subject: [PATCH 242/372] ci(asm): fix fastapi tests (#11580) httpx 0.28.0 is breaking our tests. This PR pins httpx version for fastapi threat tests. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- hatch.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatch.toml b/hatch.toml index 959551a7dbe..056ad593722 100644 --- a/hatch.toml +++ b/hatch.toml @@ -300,7 +300,7 @@ dependencies = [ "pytest-cov", "requests", "hypothesis", - "httpx", + "httpx<0.28.0", "anyio{matrix:anyio:}", "fastapi{matrix:fastapi}" ] From d4de16295426ba51cdd0f511035d230bf02d350c Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 29 Nov 2024 10:44:15 +0100 Subject: [PATCH 243/372] chore(iast): benchmarks for modulo bytes (#11588) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- benchmarks/appsec_iast_aspects/config.yaml | 21 +++++++++++++++++++++ benchmarks/appsec_iast_aspects/functions.py | 12 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/benchmarks/appsec_iast_aspects/config.yaml b/benchmarks/appsec_iast_aspects/config.yaml index 04c56704e52..0f339ab73a0 100644 --- a/benchmarks/appsec_iast_aspects/config.yaml +++ b/benchmarks/appsec_iast_aspects/config.yaml @@ -181,6 +181,27 @@ modulo_aspect: &modulo_aspect warmups: 1 function_name: "iast_modulo_aspect" +modulo_aspect_for_bytes: &modulo_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_modulo_aspect_for_bytes" + +modulo_aspect_for_bytes_bytearray: &modulo_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_modulo_aspect_for_bytes_bytearray" + +modulo_aspect_for_bytearray_bytearray: &modulo_aspect + processes: 10 + loops: 5 + values: 100 + warmups: 1 + function_name: "iast_modulo_aspect_for_bytearray_bytearray" + modulo_noaspect: <<: *modulo_aspect function_name: "modulo_noaspect" diff --git a/benchmarks/appsec_iast_aspects/functions.py b/benchmarks/appsec_iast_aspects/functions.py index 1ec76f7b8da..4e708d3ab4a 100644 --- a/benchmarks/appsec_iast_aspects/functions.py +++ b/benchmarks/appsec_iast_aspects/functions.py @@ -214,6 +214,18 @@ def iast_modulo_aspect(): return modulo_aspect("hello %s", "foo") # noqa: F821 +def iast_modulo_aspect_for_bytes(): + return modulo_aspect(b"hello %s", b"foo") # noqa: F821 + + +def iast_modulo_aspect_for_bytes_bytearray(): + return modulo_aspect(b"hello %s", bytearray(b"foo")) # noqa: F821 + + +def iast_modulo_aspect_for_bytearray_bytearray(): + return modulo_aspect(bytearray(b"hello %s"), bytearray(b"foo")) # noqa: F821 + + def modulo_noaspect(): return "{} {}".format("hello", "world") From 2e4250b12125ab873a709b0ad2d871acb4fbfa7f Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 29 Nov 2024 12:29:39 +0100 Subject: [PATCH 244/372] chore(iast): benchmarks for modulo bytes (#11589) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- benchmarks/appsec_iast_aspects/config.yaml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/benchmarks/appsec_iast_aspects/config.yaml b/benchmarks/appsec_iast_aspects/config.yaml index 0f339ab73a0..16b0c52174b 100644 --- a/benchmarks/appsec_iast_aspects/config.yaml +++ b/benchmarks/appsec_iast_aspects/config.yaml @@ -181,25 +181,16 @@ modulo_aspect: &modulo_aspect warmups: 1 function_name: "iast_modulo_aspect" -modulo_aspect_for_bytes: &modulo_aspect - processes: 10 - loops: 5 - values: 100 - warmups: 1 +modulo_aspect_for_bytes: + <<: *modulo_aspect function_name: "iast_modulo_aspect_for_bytes" -modulo_aspect_for_bytes_bytearray: &modulo_aspect - processes: 10 - loops: 5 - values: 100 - warmups: 1 +modulo_aspect_for_bytes_bytearray: + <<: *modulo_aspect function_name: "iast_modulo_aspect_for_bytes_bytearray" -modulo_aspect_for_bytearray_bytearray: &modulo_aspect - processes: 10 - loops: 5 - values: 100 - warmups: 1 +modulo_aspect_for_bytearray_bytearray: + <<: *modulo_aspect function_name: "iast_modulo_aspect_for_bytearray_bytearray" modulo_noaspect: From 2a58b9081faea295502355b6f083cd0d9b0f1f98 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 29 Nov 2024 13:08:46 +0100 Subject: [PATCH 245/372] fix(iast): ssrf vulnerability redacts the url query parameters correctly (#11563) Code Security: Ensure IAST SSRF vulnerability redacts the url query parameters correctly. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_evidence_redaction/_sensitive_handler.py | 17 +++++-- .../url_sensitive_analyzer.py | 44 +++++++++--------- ...queryparam-redaction-78834d2fedfa6bdf.yaml | 4 ++ .../test_command_injection_redacted.py | 1 + .../test_header_injection_redacted.py | 1 + .../test_sql_injection_redacted.py | 4 +- .../iast/taint_sinks/test_ssrf_redacted.py | 46 ++++++++++++++++--- 7 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/iast-fix-ssrf-queryparam-redaction-78834d2fedfa6bdf.yaml diff --git a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py index c41e56ca1c3..545f578d878 100644 --- a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py +++ b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py @@ -1,4 +1,5 @@ import re +import string from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config @@ -16,7 +17,15 @@ log = get_logger(__name__) -REDACTED_SOURCE_BUFFER = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +REDACTED_SOURCE_BUFFER = string.ascii_letters + string.digits +LEN_SOURCE_BUFFER = len(REDACTED_SOURCE_BUFFER) + + +def get_redacted_source(length): + full_repeats = length // LEN_SOURCE_BUFFER + remainder = length % LEN_SOURCE_BUFFER + result = REDACTED_SOURCE_BUFFER * full_repeats + REDACTED_SOURCE_BUFFER[:remainder] + return result class SensitiveHandler: @@ -218,7 +227,7 @@ def to_redacted_json(self, evidence_value, sensitive, tainted_ranges, sources): if source_index < len(sources): if not sources[source_index].redacted and self.is_sensible_source(sources[source_index]): redacted_sources.append(source_index) - sources[source_index].pattern = REDACTED_SOURCE_BUFFER[: len(sources[source_index].value)] + sources[source_index].pattern = get_redacted_source(len(sources[source_index].value)) sources[source_index].redacted = True if source_index in redacted_sources: @@ -282,7 +291,7 @@ def redact_source(self, sources, redacted_sources, redacted_sources_context, sou if source_index is not None: if not sources[source_index].redacted: redacted_sources.append(source_index) - sources[source_index].pattern = REDACTED_SOURCE_BUFFER[: len(sources[source_index].value)] + sources[source_index].pattern = get_redacted_source(len(sources[source_index].value)) sources[source_index].redacted = True if source_index not in redacted_sources_context.keys(): @@ -334,12 +343,12 @@ def write_redacted_value_part( sensitive_start = 0 sensitive = _value[sensitive_start : _source_redaction_context["end"] - offset] index_of_part_value_in_pattern = source.value.find(sensitive) + pattern = ( placeholder[index_of_part_value_in_pattern : index_of_part_value_in_pattern + len(sensitive)] if index_of_part_value_in_pattern > -1 else placeholder[_source_redaction_context["start"] : _source_redaction_context["end"]] ) - value_parts.append({"redacted": True, "source": source_index, "pattern": pattern}) _value = _value[len(pattern) :] offset += len(pattern) diff --git a/ddtrace/appsec/_iast/_evidence_redaction/url_sensitive_analyzer.py b/ddtrace/appsec/_iast/_evidence_redaction/url_sensitive_analyzer.py index 04ee4ecb6c8..0fab0e94cfb 100644 --- a/ddtrace/appsec/_iast/_evidence_redaction/url_sensitive_analyzer.py +++ b/ddtrace/appsec/_iast/_evidence_redaction/url_sensitive_analyzer.py @@ -4,31 +4,33 @@ log = get_logger(__name__) -AUTHORITY = r"^(?:[^:]+:)?//([^@]+)@" -QUERY_FRAGMENT = r"[?#&]([^=&;]+)=([^?#&]+)" -pattern = re.compile(f"({AUTHORITY})|({QUERY_FRAGMENT})", re.IGNORECASE | re.MULTILINE) +AUTHORITY_PATTERN = re.compile(r"https?://([^@]+)(?=@)", re.IGNORECASE | re.MULTILINE) +QUERY_FRAGMENT_PATTERN = re.compile(r"[?#&]([^=&;]+)=([^?#&]+)", re.IGNORECASE | re.MULTILINE) -def url_sensitive_analyzer(evidence, name_pattern=None, value_pattern=None): - try: - ranges = [] - regex_result = pattern.search(evidence.value) +def find_authority(ranges, evidence): + regex_result = AUTHORITY_PATTERN.search(evidence.value) + while regex_result is not None: + if isinstance(regex_result.group(1), str): + start = regex_result.start(1) + end = regex_result.start(1) + (len(regex_result.group(1))) + ranges.append({"start": start, "end": end}) - while regex_result is not None: - if isinstance(regex_result.group(1), str): - end = regex_result.start() + (len(regex_result.group(0)) - 1) - start = end - len(regex_result.group(1)) - ranges.append({"start": start, "end": end}) + regex_result = AUTHORITY_PATTERN.search(evidence.value, regex_result.end()) - if isinstance(regex_result.group(3), str): - end = regex_result.start() + len(regex_result.group(0)) - start = end - len(regex_result.group(3)) - ranges.append({"start": start, "end": end}) - regex_result = pattern.search(evidence.value, regex_result.end()) +def find_query_fragment(ranges, evidence): + regex_result = QUERY_FRAGMENT_PATTERN.search(evidence.value) + while regex_result is not None: + if isinstance(regex_result.group(2), str): + start = regex_result.start(2) + end = regex_result.start(2) + (len(regex_result.group(2))) + ranges.append({"start": start, "end": end}) + regex_result = QUERY_FRAGMENT_PATTERN.search(evidence.value, regex_result.end()) - return ranges - except Exception as e: - log.debug(e) - return [] +def url_sensitive_analyzer(evidence, name_pattern=None, value_pattern=None): + ranges = [] + find_authority(ranges, evidence) + find_query_fragment(ranges, evidence) + return ranges diff --git a/releasenotes/notes/iast-fix-ssrf-queryparam-redaction-78834d2fedfa6bdf.yaml b/releasenotes/notes/iast-fix-ssrf-queryparam-redaction-78834d2fedfa6bdf.yaml new file mode 100644 index 00000000000..5d22e5a5afb --- /dev/null +++ b/releasenotes/notes/iast-fix-ssrf-queryparam-redaction-78834d2fedfa6bdf.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: Ensure IAST SSRF vulnerability redacts the url query parameters correctly. diff --git a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py index 6d42d0ccae2..f1e2b98089c 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py @@ -40,6 +40,7 @@ def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_ source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_CMDI + assert vulnerability["evidence"] == vulnerabilities_expected["evidence"] assert source == sources_expected diff --git a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py index 8ee57da6334..d47433f7745 100644 --- a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py @@ -123,4 +123,5 @@ def test_header_injection_redaction_suite( source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_HEADER_INJECTION + assert vulnerability["evidence"] == vulnerabilities_expected["evidence"] assert source == sources_expected diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index faaa6fc0ee7..ba6675e7531 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -19,7 +19,9 @@ # FIXME: ideally all these should pass, through the key is that we don't leak any potential PII -_ignore_list = {46, 47} +_ignore_list = { + 46, +} @pytest.mark.parametrize( diff --git a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py index 256df8f079a..aa316ab3b02 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py @@ -21,10 +21,9 @@ @pytest.mark.parametrize( - "evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_SSRF))[0:2] + "evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_SSRF, ignore_list={9, 10})) ) def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): - # TODO: fix get_parametrize(VULN_SSRF)[2:] replacements doesn't work correctly with params of SSRF tainted_object = evidence_input_value = evidence_input.get("value", "") if evidence_input_value: tainted_object = _taint_pyobject_multiranges( @@ -49,6 +48,7 @@ def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_ source["origin"] = origin_to_str(source["origin"]) assert vulnerability["type"] == VULN_SSRF + assert vulnerability["evidence"] == vulnerabilities_expected["evidence"] assert source == sources_expected @@ -71,15 +71,44 @@ def test_ssrf_redact_param(iast_context_defaults): assert result["vulnerabilities"] for v in result["vulnerabilities"]: assert v["evidence"]["valueParts"] == [ - {"value": "https://www.domain1.com/"}, - {"redacted": True}, + {"value": "https://www.domain1.com/?id="}, {"pattern": "abcdefgh", "redacted": True, "source": 0}, + {"value": "¶m2="}, {"redacted": True}, + {"value": "¶m3="}, {"redacted": True}, + {"value": "¶m3="}, {"redacted": True}, ] +def test_ssrf_redact_params_log_url(iast_context_defaults): + url = ( + "http://dovahkiin.dovahkiin.naal.ok/zin/los/vahriin/wahdein-vokul-mahfaeraak.ast-vaal-4ee5-aa01-3de356fd2cd8/snapshots/" + "f7591b64-9839-4be8-a3a9-e7ccf5c0e17e?schemaVersion=c27f6149-36b6-4f3a-bacb-ba4b8db36892" + ) + tainted_source = taint_pyobject(pyobject=url, source_name="encoded_forward_api", source_value=url) + + ev = Evidence(value=tainted_source) + + loc = Location(path="foobar.py", line=35, spanId=123) + v = Vulnerability(type=VULN_SSRF, evidence=ev, location=loc) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + for v in result["vulnerabilities"]: + assert v["evidence"]["valueParts"] == [ + { + "source": 0, + "value": "http://dovahkiin.dovahkiin.naal.ok/zin/los/vahriin/wahdein-vokul-mahfaeraak.ast-vaal-4ee5-aa01-3de356fd" + "2cd8/snapshots/f7591b64-9839-4be8-a3a9-e7ccf5c0e17e?schemaVersion=", + }, + {"pattern": "TUVWXYZ0123456789abcdefghijklmnopqrs", "redacted": True, "source": 0}, + ] + + def test_cmdi_redact_user_password(iast_context_defaults): user_taint_range = taint_pyobject(pyobject="root", source_name="username", source_value="root") password_taint_range = taint_pyobject( @@ -107,7 +136,12 @@ def test_cmdi_redact_user_password(iast_context_defaults): assert v["evidence"]["valueParts"] == [ {"value": "https://"}, {"pattern": "abcd", "redacted": True, "source": 0}, - {"value": ":"}, + {"redacted": True}, {"pattern": "abcdefghijklmnopqrs", "redacted": True, "source": 1}, - {"value": "@domain1.com/?id=¶m2=value2¶m3=value3¶m3=value3"}, + {"value": "@domain1.com/?id=¶m2="}, + {"redacted": True}, + {"value": "¶m3="}, + {"redacted": True}, + {"value": "¶m3="}, + {"redacted": True}, ] From b6ff124c71e3c0081cb0959f6f947ae70effbc05 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 29 Nov 2024 13:11:53 +0100 Subject: [PATCH 246/372] fix(iast): add psycopg and psycopg2 to denylist (#11571) Code security: This fix resolves an issue where the patching of psycopg is producing bad code. Since it's not required to patch psycopg or psycopg2 modules, we will avoid patching them altogether, with the benefit of a small performance improvement. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/_ast/ast_patching.py | 3 +++ .../fix-iast-add-psycopg-to-denylist-a88961e04125e674.yaml | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/notes/fix-iast-add-psycopg-to-denylist-a88961e04125e674.yaml diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index eca157eb3a5..6a1e4c2d3b6 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -280,6 +280,9 @@ "pkg_resources.", "pluggy.", "protobuf.", + "psycopg.", # PostgreSQL adapter for Python (v3) + "_psycopg.", # PostgreSQL adapter for Python (v3) + "psycopg2.", # PostgreSQL adapter for Python (v2) "pycparser.", # this package is called when a module is imported, propagation is not needed "pytest.", # Testing framework "_pytest.", diff --git a/releasenotes/notes/fix-iast-add-psycopg-to-denylist-a88961e04125e674.yaml b/releasenotes/notes/fix-iast-add-psycopg-to-denylist-a88961e04125e674.yaml new file mode 100644 index 00000000000..ad7d174986a --- /dev/null +++ b/releasenotes/notes/fix-iast-add-psycopg-to-denylist-a88961e04125e674.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code security: This fix resolves a patching issue with `psycopg3`. From 501f5a018c87c0a371593be5a119de84c9e0e7e0 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 29 Nov 2024 16:42:36 +0100 Subject: [PATCH 247/372] fix(iast): fix propagation error in modulo operator (#11573) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- benchmarks/bm/iast_fixtures/str_methods.py | 15 +++ .../_taint_tracking/Aspects/AspectModulo.cpp | 44 ++------ ...st-propagation-error-6a36612724b8c84c.yaml | 5 + .../aspects/test_modulo_aspect_fixtures.py | 103 ++++++++++++++++++ 4 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 releasenotes/notes/iast-fix-iast-propagation-error-6a36612724b8c84c.yaml diff --git a/benchmarks/bm/iast_fixtures/str_methods.py b/benchmarks/bm/iast_fixtures/str_methods.py index f1960f0ae77..4a7c63f6fda 100644 --- a/benchmarks/bm/iast_fixtures/str_methods.py +++ b/benchmarks/bm/iast_fixtures/str_methods.py @@ -875,6 +875,21 @@ def do_args_kwargs_4(format_string, *args_safe, **kwargs_safe) -> Text: return format_string.format("1", "2", test_kwarg=3, *args_safe, **kwargs_safe) +def psycopg_queries_dump_bytes(args: tuple) -> bytes: + template = b'INSERT INTO "show_client" ("username") VALUES (%s) RETURNING "show_client"."id"' + return template % args + + +def psycopg_queries_dump_bytes_with_keys(args: dict) -> bytes: + template = b'INSERT INTO "show_client" ("username") VALUES (%(name)s) RETURNING "show_client"."id"' + return template % args + + +def psycopg_queries_dump_bytearray(args: tuple) -> bytes: + template = b'INSERT INTO "show_client" ("username") VALUES (%s) RETURNING "show_client"."id"' + return template % args + + def do_format_key_error(param1: str) -> Text: return "Test {param1}, {param2}".format(param1=param1) # noqa:F524 diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp index a8efdd1b0fb..a08f76d9f3d 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp @@ -20,45 +20,17 @@ do_modulo(PyObject* text, PyObject* insert_tuple_or_obj) if (PyUnicode_Check(text)) { result = PyUnicode_Format(text, insert_tuple); - } else if (PyBytes_Check(text)) { - if (PyObject* text_unicode = PyUnicode_FromEncodedObject(text, "utf-8", "strict"); text_unicode != nullptr) { - result = PyUnicode_Format(text_unicode, insert_tuple); - Py_DECREF(text_unicode); - - if (result != nullptr) { - PyObject* encoded_result = PyUnicode_AsEncodedString(result, "utf-8", "strict"); - Py_DECREF(result); - result = encoded_result; - } - } - } else if (PyByteArray_Check(text)) { - if (PyObject* text_bytes = PyBytes_FromStringAndSize(PyByteArray_AsString(text), PyByteArray_Size(text)); - text_bytes != nullptr) { - PyObject* text_unicode = PyUnicode_FromEncodedObject(text_bytes, "utf-8", "strict"); - Py_DECREF(text_bytes); - if (text_unicode != nullptr) { - result = PyUnicode_Format(text_unicode, insert_tuple); - Py_DECREF(text_unicode); - - if (result != nullptr) { - PyObject* encoded_result = PyUnicode_AsEncodedString(result, "utf-8", "strict"); - Py_DECREF(result); - result = encoded_result; - } - } - } - - if (result != nullptr) { - PyObject* result_bytearray = PyByteArray_FromObject(result); - Py_DECREF(result); - result = result_bytearray; - } + } else if (PyBytes_Check(text) or PyByteArray_Check(text)) { + auto method_name = PyUnicode_FromString("__mod__"); + result = PyObject_CallMethodObjArgs(text, method_name, insert_tuple, nullptr); + Py_DECREF(method_name); } else { - Py_DECREF(insert_tuple); - return nullptr; } - Py_DECREF(insert_tuple); + if (has_pyerr()) { + Py_XDECREF(result); + return nullptr; + } return result; } diff --git a/releasenotes/notes/iast-fix-iast-propagation-error-6a36612724b8c84c.yaml b/releasenotes/notes/iast-fix-iast-propagation-error-6a36612724b8c84c.yaml new file mode 100644 index 00000000000..9f650cc6914 --- /dev/null +++ b/releasenotes/notes/iast-fix-iast-propagation-error-6a36612724b8c84c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Code Security: This fix resolves an issue where the modulo (%) operator would not be replaced correctly for bytes + and bytesarray if IAST is enabled. diff --git a/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py b/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py index 64eb7b6c1b6..80ca12a2db8 100644 --- a/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py @@ -4,10 +4,15 @@ from typing import List # noqa:F401 from typing import Text # noqa:F401 +from hypothesis import given +from hypothesis.strategies import text import pytest +from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence from ddtrace.appsec._iast._taint_tracking import get_ranges +from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.conftest import _iast_patched_module @@ -175,3 +180,101 @@ def test_modulo_when_parameter_value_already_present_in_template_then_range_is_c expected_result="aaaaaaaaaaaa", escaped_expected_result="aaaaaaa:+-a-+:aaaa", ) + + +@pytest.mark.parametrize("is_tainted", [True, False]) +@given(text()) +def test_psycopg_queries_dump_bytes(is_tainted, string_data): + string_data_to_bytes = string_data.encode("utf-8") + bytes_to_test_orig = b"'%s'" % (string_data.encode("utf-8")) + if is_tainted: + bytes_to_test = taint_pyobject( + pyobject=bytes_to_test_orig, + source_name="string_data_to_bytes", + source_value=bytes_to_test_orig, + source_origin=OriginType.PARAMETER, + ) + else: + bytes_to_test = bytes_to_test_orig + + result = mod.psycopg_queries_dump_bytes((bytes_to_test,)) + assert ( + result + == b'INSERT INTO "show_client" ("username") VALUES (\'%s\') RETURNING "show_client"."id"' % string_data_to_bytes + ) + + if is_tainted and string_data_to_bytes: + ranges = get_tainted_ranges(result) + assert len(ranges) == 1 + assert ranges[0].start == 47 + assert ranges[0].length == len(bytes_to_test_orig) + assert result[ranges[0].start : (ranges[0].start + ranges[0].length)] == bytes_to_test_orig + + result = mod.psycopg_queries_dump_bytes_with_keys({b"name": bytes_to_test}) + assert ( + result + == b'INSERT INTO "show_client" ("username") VALUES (\'%s\') RETURNING "show_client"."id"' % string_data_to_bytes + ) + + with pytest.raises(TypeError): + mod.psycopg_queries_dump_bytes( + ( + bytes_to_test, + bytes_to_test, + ) + ) + + with pytest.raises(TypeError): + _ = b'INSERT INTO "show_client" ("username") VALUES (%s) RETURNING "show_client"."id"' % ((1,)) + + with pytest.raises(TypeError): + mod.psycopg_queries_dump_bytes((1,)) + + with pytest.raises(KeyError): + _ = b'INSERT INTO "show_client" ("username") VALUES (%(name)s) RETURNING "show_client"."id"' % { + "name": bytes_to_test + } + + with pytest.raises(KeyError): + mod.psycopg_queries_dump_bytes_with_keys({"name": bytes_to_test}) + + +@pytest.mark.parametrize("is_tainted", [True, False]) +@given(text()) +def test_psycopg_queries_dump_bytearray(is_tainted, string_data): + string_data_to_bytesarray = bytearray(string_data.encode("utf-8")) + bytesarray_to_test_orig = bytearray(b"'%s'" % (string_data.encode("utf-8"))) + if is_tainted: + bytesarray_to_test = taint_pyobject( + pyobject=bytesarray_to_test_orig, + source_name="string_data_to_bytes", + source_value=bytesarray_to_test_orig, + source_origin=OriginType.PARAMETER, + ) + else: + bytesarray_to_test = bytesarray_to_test_orig + + result = mod.psycopg_queries_dump_bytearray((bytesarray_to_test,)) + assert ( + result + == b'INSERT INTO "show_client" ("username") VALUES (\'%s\') RETURNING "show_client"."id"' + % string_data_to_bytesarray + ) + + if is_tainted and string_data_to_bytesarray: + ranges = get_tainted_ranges(result) + assert len(ranges) == 1 + assert ranges[0].start == 47 + assert ranges[0].length == len(bytesarray_to_test_orig) + assert result[ranges[0].start : (ranges[0].start + ranges[0].length)] == bytesarray_to_test_orig + + with pytest.raises(TypeError): + mod.psycopg_queries_dump_bytearray( + ( + bytesarray_to_test, + bytesarray_to_test, + ) + ) + + with pytest.raises(TypeError): + mod.psycopg_queries_dump_bytearray((1,)) From 033006f91de484e07aae8b3cf56697ccdf8f3bb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:48:23 +0000 Subject: [PATCH 248/372] chore: update anthropic latest version to 0.40.0 (#11602) Update anthropic lockfiles and dependency package lockfiles. This performs the following updates: 1) Some anthropic lockfiles use anthropic `latest`. This will update anthropic and dependencies. 2) Some anthropic lockfiles use a pinned (non-latest) version of anthropic, but require the `latest` version of another package. This will update all such packages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/11031a2.txt | 31 ++++++++++++------------------- .riot/requirements/13e5e9e.txt | 29 +++++++++++------------------ .riot/requirements/149b454.txt | 33 +++++++++++++-------------------- .riot/requirements/160ef02.txt | 33 +++++++++++++-------------------- .riot/requirements/1d5589b.txt | 33 +++++++++++++-------------------- .riot/requirements/1d8be99.txt | 29 +++++++++++------------------ .riot/requirements/1e0e8e5.txt | 33 +++++++++++++-------------------- .riot/requirements/3c4b933.txt | 31 ++++++++++++------------------- .riot/requirements/6bff0b2.txt | 31 ++++++++++++------------------- .riot/requirements/87ce77a.txt | 31 ++++++++++++------------------- 10 files changed, 122 insertions(+), 192 deletions(-) diff --git a/.riot/requirements/11031a2.txt b/.riot/requirements/11031a2.txt index 14260069939..14cc7570e2e 100644 --- a/.riot/requirements/11031a2.txt +++ b/.riot/requirements/11031a2.txt @@ -5,43 +5,36 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/11031a2.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tqdm==4.66.5 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/13e5e9e.txt b/.riot/requirements/13e5e9e.txt index 6ca5e468b07..d0a9e22fa16 100644 --- a/.riot/requirements/13e5e9e.txt +++ b/.riot/requirements/13e5e9e.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/13e5e9e.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.5.0 +anthropic==0.40.0 +anyio==4.5.2 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 coverage[toml]==7.6.1 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==1.26.20 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.15.2 diff --git a/.riot/requirements/149b454.txt b/.riot/requirements/149b454.txt index 2f2f5ea025f..684121d544e 100644 --- a/.riot/requirements/149b454.txt +++ b/.riot/requirements/149b454.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/149b454.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==1.26.20 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/160ef02.txt b/.riot/requirements/160ef02.txt index 001d922d7b7..9aedf28d2d6 100644 --- a/.riot/requirements/160ef02.txt +++ b/.riot/requirements/160ef02.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/160ef02.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==1.26.20 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/1d5589b.txt b/.riot/requirements/1d5589b.txt index c07e6fd20ea..21faae39543 100644 --- a/.riot/requirements/1d5589b.txt +++ b/.riot/requirements/1d5589b.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d5589b.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/1d8be99.txt b/.riot/requirements/1d8be99.txt index 7a93a1e8f0a..1b0576cdb2d 100644 --- a/.riot/requirements/1d8be99.txt +++ b/.riot/requirements/1d8be99.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d8be99.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.5.0 +anthropic==0.40.0 +anyio==4.5.2 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 coverage[toml]==7.6.1 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==1.26.20 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.15.2 diff --git a/.riot/requirements/1e0e8e5.txt b/.riot/requirements/1e0e8e5.txt index cf34af1a67e..ed2999fedb2 100644 --- a/.riot/requirements/1e0e8e5.txt +++ b/.riot/requirements/1e0e8e5.txt @@ -5,45 +5,38 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1e0e8e5.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 exceptiongroup==1.2.2 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tomli==2.0.2 -tqdm==4.66.5 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/3c4b933.txt b/.riot/requirements/3c4b933.txt index b0f6120b9d1..cf513715ddf 100644 --- a/.riot/requirements/3c4b933.txt +++ b/.riot/requirements/3c4b933.txt @@ -5,43 +5,36 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/3c4b933.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tqdm==4.66.5 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/6bff0b2.txt b/.riot/requirements/6bff0b2.txt index 7584df87cf3..523326619bf 100644 --- a/.riot/requirements/6bff0b2.txt +++ b/.riot/requirements/6bff0b2.txt @@ -5,43 +5,36 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/6bff0b2.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tqdm==4.66.5 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 diff --git a/.riot/requirements/87ce77a.txt b/.riot/requirements/87ce77a.txt index 1fa837a1c56..d799f76ef6b 100644 --- a/.riot/requirements/87ce77a.txt +++ b/.riot/requirements/87ce77a.txt @@ -5,43 +5,36 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/87ce77a.in # annotated-types==0.7.0 -anthropic==0.36.0 -anyio==4.6.0 +anthropic==0.40.0 +anyio==4.6.2.post1 attrs==24.2.0 certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.2 +coverage[toml]==7.6.8 distro==1.9.0 -filelock==3.16.1 -fsspec==2024.9.0 h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -huggingface-hub==0.25.2 +httpcore==1.0.7 +httpx==0.28.0 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 -jiter==0.6.1 +jiter==0.8.0 mock==5.1.0 multidict==6.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pyyaml==6.0.2 -requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -tokenizers==0.20.0 -tqdm==4.66.5 typing-extensions==4.12.2 urllib3==2.2.3 vcrpy==6.0.2 -wrapt==1.16.0 -yarl==1.14.0 +wrapt==1.17.0 +yarl==1.18.0 From b89db9fa28e04141b1a2fd46ec9751d9b63a63d7 Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:18:04 -0500 Subject: [PATCH 249/372] feat(llmobs): [MLOB-1918] submit llmobs payloads from vertexai integration (#11528) This PR enables submitting LLMObs spans from the Vertex AI integration. APM tracing support for the `generate_content` / `generate_content_async` / `send_message` / `send_message_async` of the Vertex AI library was added in an earlier [PR](https://github.com/DataDog/dd-trace-py/pull/11236). This PR builds off of that to additionally send LLMObs span events to LLM Observability. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{f12fa99.txt => 102c18b.txt} | 9 +- .../requirements/{23eab38.txt => 107d415.txt} | 9 +- .../requirements/{692fe7a.txt => 1b0d9c1.txt} | 9 +- .riot/requirements/1bee666.txt | 64 ++ .riot/requirements/1e15a25.txt | 48 -- .riot/requirements/1f54e6b.txt | 50 -- .riot/requirements/55b8536.txt | 62 ++ .riot/requirements/6820ef2.txt | 62 ++ .riot/requirements/ab2f587.txt | 64 ++ .../requirements/{59e23ef.txt => bf4cae6.txt} | 9 +- .riot/requirements/e8247d6.txt | 48 -- .riot/requirements/ebe4ea5.txt | 50 -- .../internal/google_generativeai/_utils.py | 9 +- ddtrace/contrib/internal/vertexai/_utils.py | 46 +- ddtrace/contrib/internal/vertexai/patch.py | 24 +- ddtrace/llmobs/_constants.py | 1 + ddtrace/llmobs/_integrations/bedrock.py | 7 +- ddtrace/llmobs/_integrations/gemini.py | 71 +- ddtrace/llmobs/_integrations/utils.py | 91 +++ ddtrace/llmobs/_integrations/vertexai.py | 119 +++ ddtrace/llmobs/_llmobs.py | 1 + ddtrace/llmobs/_utils.py | 3 +- ...feat-llmobs-vertexai-f58488859472c7b5.yaml | 5 + riotfile.py | 3 + tests/contrib/vertexai/conftest.py | 32 +- .../contrib/vertexai/test_vertexai_llmobs.py | 698 ++++++++++++++++++ 26 files changed, 1281 insertions(+), 313 deletions(-) rename .riot/requirements/{f12fa99.txt => 102c18b.txt} (90%) rename .riot/requirements/{23eab38.txt => 107d415.txt} (90%) rename .riot/requirements/{692fe7a.txt => 1b0d9c1.txt} (90%) create mode 100644 .riot/requirements/1bee666.txt delete mode 100644 .riot/requirements/1e15a25.txt delete mode 100644 .riot/requirements/1f54e6b.txt create mode 100644 .riot/requirements/55b8536.txt create mode 100644 .riot/requirements/6820ef2.txt create mode 100644 .riot/requirements/ab2f587.txt rename .riot/requirements/{59e23ef.txt => bf4cae6.txt} (90%) delete mode 100644 .riot/requirements/e8247d6.txt delete mode 100644 .riot/requirements/ebe4ea5.txt create mode 100644 releasenotes/notes/feat-llmobs-vertexai-f58488859472c7b5.yaml create mode 100644 tests/contrib/vertexai/test_vertexai_llmobs.py diff --git a/.riot/requirements/f12fa99.txt b/.riot/requirements/102c18b.txt similarity index 90% rename from .riot/requirements/f12fa99.txt rename to .riot/requirements/102c18b.txt index 6ba3725dc6a..60b40418ee3 100644 --- a/.riot/requirements/f12fa99.txt +++ b/.riot/requirements/102c18b.txt @@ -2,15 +2,16 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f12fa99.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/102c18b.in # annotated-types==0.7.0 attrs==24.2.0 cachetools==5.5.0 certifi==2024.8.30 charset-normalizer==3.4.0 -coverage[toml]==7.6.7 +coverage[toml]==7.6.8 docstring-parser==0.16 +google-ai-generativelanguage==0.6.13 google-api-core[grpc]==2.23.0 google-auth==2.36.0 google-cloud-aiplatform[all]==1.71.1 @@ -36,8 +37,8 @@ proto-plus==1.25.0 protobuf==5.28.3 pyasn1==0.6.1 pyasn1-modules==0.4.1 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==6.0.0 diff --git a/.riot/requirements/23eab38.txt b/.riot/requirements/107d415.txt similarity index 90% rename from .riot/requirements/23eab38.txt rename to .riot/requirements/107d415.txt index feed5849537..4a242f86894 100644 --- a/.riot/requirements/23eab38.txt +++ b/.riot/requirements/107d415.txt @@ -2,15 +2,16 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/23eab38.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/107d415.in # annotated-types==0.7.0 attrs==24.2.0 cachetools==5.5.0 certifi==2024.8.30 charset-normalizer==3.4.0 -coverage[toml]==7.6.7 +coverage[toml]==7.6.8 docstring-parser==0.16 +google-ai-generativelanguage==0.6.13 google-api-core[grpc]==2.23.0 google-auth==2.36.0 google-cloud-aiplatform[all]==1.71.1 @@ -36,8 +37,8 @@ proto-plus==1.25.0 protobuf==5.28.3 pyasn1==0.6.1 pyasn1-modules==0.4.1 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==6.0.0 diff --git a/.riot/requirements/692fe7a.txt b/.riot/requirements/1b0d9c1.txt similarity index 90% rename from .riot/requirements/692fe7a.txt rename to .riot/requirements/1b0d9c1.txt index 2e2a00ab4e3..842077d8b3d 100644 --- a/.riot/requirements/692fe7a.txt +++ b/.riot/requirements/1b0d9c1.txt @@ -2,16 +2,17 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/692fe7a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b0d9c1.in # annotated-types==0.7.0 attrs==24.2.0 cachetools==5.5.0 certifi==2024.8.30 charset-normalizer==3.4.0 -coverage[toml]==7.6.7 +coverage[toml]==7.6.8 docstring-parser==0.16 exceptiongroup==1.2.2 +google-ai-generativelanguage==0.6.13 google-api-core[grpc]==2.23.0 google-auth==2.36.0 google-cloud-aiplatform[all]==1.71.1 @@ -37,8 +38,8 @@ proto-plus==1.25.0 protobuf==5.28.3 pyasn1==0.6.1 pyasn1-modules==0.4.1 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==6.0.0 diff --git a/.riot/requirements/1bee666.txt b/.riot/requirements/1bee666.txt new file mode 100644 index 00000000000..70c923d2825 --- /dev/null +++ b/.riot/requirements/1bee666.txt @@ -0,0 +1,64 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bee666.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +docstring-parser==0.16 +exceptiongroup==1.2.2 +google-ai-generativelanguage==0.6.10 +google-api-core[grpc]==2.23.0 +google-api-python-client==2.154.0 +google-auth==2.36.0 +google-auth-httplib2==0.2.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-generativeai==0.8.3 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +httplib2==0.22.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.0.2 +opentracing==2.4.0 +packaging==24.2 +pillow==11.0.0 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.10.2 +pydantic-core==2.27.1 +pyparsing==3.2.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +tqdm==4.67.1 +typing-extensions==4.12.2 +uritemplate==4.1.1 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/1e15a25.txt b/.riot/requirements/1e15a25.txt deleted file mode 100644 index 36405478a02..00000000000 --- a/.riot/requirements/1e15a25.txt +++ /dev/null @@ -1,48 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e15a25.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -google-ai-generativelanguage==0.6.9 -google-api-core[grpc]==2.19.2 -google-api-python-client==2.145.0 -google-auth==2.34.0 -google-auth-httplib2==0.2.0 -google-generativeai==0.8.0 -googleapis-common-protos==1.65.0 -grpcio==1.66.1 -grpcio-status==1.66.1 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.8 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pillow==10.4.0 -pluggy==1.5.0 -proto-plus==1.24.0 -protobuf==5.28.0 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pyparsing==3.1.4 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -rsa==4.9 -sortedcontainers==2.4.0 -tqdm==4.66.5 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.2 diff --git a/.riot/requirements/1f54e6b.txt b/.riot/requirements/1f54e6b.txt deleted file mode 100644 index 8bcc57eabff..00000000000 --- a/.riot/requirements/1f54e6b.txt +++ /dev/null @@ -1,50 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f54e6b.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -google-ai-generativelanguage==0.6.9 -google-api-core[grpc]==2.19.2 -google-api-python-client==2.145.0 -google-auth==2.34.0 -google-auth-httplib2==0.2.0 -google-generativeai==0.8.0 -googleapis-common-protos==1.65.0 -grpcio==1.66.1 -grpcio-status==1.66.1 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.8 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pillow==10.4.0 -pluggy==1.5.0 -proto-plus==1.24.0 -protobuf==5.28.0 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pyparsing==3.1.4 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -rsa==4.9 -sortedcontainers==2.4.0 -tomli==2.0.1 -tqdm==4.66.5 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.2 diff --git a/.riot/requirements/55b8536.txt b/.riot/requirements/55b8536.txt new file mode 100644 index 00000000000..ed6036adcd1 --- /dev/null +++ b/.riot/requirements/55b8536.txt @@ -0,0 +1,62 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/55b8536.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +docstring-parser==0.16 +google-ai-generativelanguage==0.6.10 +google-api-core[grpc]==2.23.0 +google-api-python-client==2.154.0 +google-auth==2.36.0 +google-auth-httplib2==0.2.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-generativeai==0.8.3 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +httplib2==0.22.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pillow==11.0.0 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.10.2 +pydantic-core==2.27.1 +pyparsing==3.2.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.12.2 +uritemplate==4.1.1 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/6820ef2.txt b/.riot/requirements/6820ef2.txt new file mode 100644 index 00000000000..2db99b509e5 --- /dev/null +++ b/.riot/requirements/6820ef2.txt @@ -0,0 +1,62 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6820ef2.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +docstring-parser==0.16 +google-ai-generativelanguage==0.6.10 +google-api-core[grpc]==2.23.0 +google-api-python-client==2.154.0 +google-auth==2.36.0 +google-auth-httplib2==0.2.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-generativeai==0.8.3 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +httplib2==0.22.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pillow==11.0.0 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.10.2 +pydantic-core==2.27.1 +pyparsing==3.2.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.12.2 +uritemplate==4.1.1 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/ab2f587.txt b/.riot/requirements/ab2f587.txt new file mode 100644 index 00000000000..29fd2375edd --- /dev/null +++ b/.riot/requirements/ab2f587.txt @@ -0,0 +1,64 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ab2f587.in +# +annotated-types==0.7.0 +attrs==24.2.0 +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +docstring-parser==0.16 +exceptiongroup==1.2.2 +google-ai-generativelanguage==0.6.10 +google-api-core[grpc]==2.23.0 +google-api-python-client==2.154.0 +google-auth==2.36.0 +google-auth-httplib2==0.2.0 +google-cloud-aiplatform[all]==1.71.1 +google-cloud-bigquery==3.27.0 +google-cloud-core==2.4.1 +google-cloud-resource-manager==1.13.1 +google-cloud-storage==2.18.2 +google-crc32c==1.6.0 +google-generativeai==0.8.3 +google-resumable-media==2.7.2 +googleapis-common-protos[grpc]==1.66.0 +grpc-google-iam-v1==0.13.1 +grpcio==1.68.0 +grpcio-status==1.68.0 +httplib2==0.22.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +numpy==2.1.3 +opentracing==2.4.0 +packaging==24.2 +pillow==11.0.0 +pluggy==1.5.0 +proto-plus==1.25.0 +protobuf==5.28.3 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pydantic==2.10.2 +pydantic-core==2.27.1 +pyparsing==3.2.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +rsa==4.9 +shapely==2.0.6 +six==1.16.0 +sortedcontainers==2.4.0 +tomli==2.1.0 +tqdm==4.67.1 +typing-extensions==4.12.2 +uritemplate==4.1.1 +urllib3==2.2.3 +vertexai==1.71.1 diff --git a/.riot/requirements/59e23ef.txt b/.riot/requirements/bf4cae6.txt similarity index 90% rename from .riot/requirements/59e23ef.txt rename to .riot/requirements/bf4cae6.txt index a0284fd7200..17acda0e74d 100644 --- a/.riot/requirements/59e23ef.txt +++ b/.riot/requirements/bf4cae6.txt @@ -2,16 +2,17 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/59e23ef.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bf4cae6.in # annotated-types==0.7.0 attrs==24.2.0 cachetools==5.5.0 certifi==2024.8.30 charset-normalizer==3.4.0 -coverage[toml]==7.6.7 +coverage[toml]==7.6.8 docstring-parser==0.16 exceptiongroup==1.2.2 +google-ai-generativelanguage==0.6.13 google-api-core[grpc]==2.23.0 google-auth==2.36.0 google-cloud-aiplatform[all]==1.71.1 @@ -37,8 +38,8 @@ proto-plus==1.25.0 protobuf==5.28.3 pyasn1==0.6.1 pyasn1-modules==0.4.1 -pydantic==2.9.2 -pydantic-core==2.23.4 +pydantic==2.10.2 +pydantic-core==2.27.1 pytest==8.3.3 pytest-asyncio==0.24.0 pytest-cov==6.0.0 diff --git a/.riot/requirements/e8247d6.txt b/.riot/requirements/e8247d6.txt deleted file mode 100644 index 2aad3bb1a89..00000000000 --- a/.riot/requirements/e8247d6.txt +++ /dev/null @@ -1,48 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e8247d6.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -google-ai-generativelanguage==0.6.9 -google-api-core[grpc]==2.19.2 -google-api-python-client==2.145.0 -google-auth==2.34.0 -google-auth-httplib2==0.2.0 -google-generativeai==0.8.0 -googleapis-common-protos==1.65.0 -grpcio==1.66.1 -grpcio-status==1.66.1 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.8 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pillow==10.4.0 -pluggy==1.5.0 -proto-plus==1.24.0 -protobuf==5.28.0 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pyparsing==3.1.4 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -rsa==4.9 -sortedcontainers==2.4.0 -tqdm==4.66.5 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.2 diff --git a/.riot/requirements/ebe4ea5.txt b/.riot/requirements/ebe4ea5.txt deleted file mode 100644 index 264c2960158..00000000000 --- a/.riot/requirements/ebe4ea5.txt +++ /dev/null @@ -1,50 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ebe4ea5.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -google-ai-generativelanguage==0.6.9 -google-api-core[grpc]==2.19.2 -google-api-python-client==2.145.0 -google-auth==2.34.0 -google-auth-httplib2==0.2.0 -google-generativeai==0.8.0 -googleapis-common-protos==1.65.0 -grpcio==1.66.1 -grpcio-status==1.66.1 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.8 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pillow==10.4.0 -pluggy==1.5.0 -proto-plus==1.24.0 -protobuf==5.28.0 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pyparsing==3.1.4 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -rsa==4.9 -sortedcontainers==2.4.0 -tomli==2.0.1 -tqdm==4.66.5 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.2 diff --git a/ddtrace/contrib/internal/google_generativeai/_utils.py b/ddtrace/contrib/internal/google_generativeai/_utils.py index 20a923e07cb..ad281c4e847 100644 --- a/ddtrace/contrib/internal/google_generativeai/_utils.py +++ b/ddtrace/contrib/internal/google_generativeai/_utils.py @@ -5,6 +5,7 @@ from ddtrace.internal.utils import get_argument_value from ddtrace.llmobs._integrations.utils import get_generation_config_google +from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model from ddtrace.llmobs._integrations.utils import tag_request_content_part_google from ddtrace.llmobs._integrations.utils import tag_response_part_google @@ -109,7 +110,7 @@ def tag_request(span, integration, instance, args, kwargs): """ contents = get_argument_value(args, kwargs, 0, "contents") generation_config = get_generation_config_google(instance, kwargs) - system_instruction = getattr(instance, "_system_instruction", None) + system_instruction = get_system_instructions_from_google_model(instance) stream = kwargs.get("stream", None) try: @@ -127,10 +128,8 @@ def tag_request(span, integration, instance, args, kwargs): return if system_instruction: - for idx, part in enumerate(system_instruction.parts): - span.set_tag_str( - "google_generativeai.request.system_instruction.%d.text" % idx, integration.trunc(str(part.text)) - ) + for idx, text in enumerate(system_instruction): + span.set_tag_str("google_generativeai.request.system_instruction.%d.text" % idx, integration.trunc(text)) if isinstance(contents, str): span.set_tag_str("google_generativeai.request.contents.0.text", integration.trunc(contents)) diff --git a/ddtrace/contrib/internal/vertexai/_utils.py b/ddtrace/contrib/internal/vertexai/_utils.py index 07fd0cb69e2..129b97fd920 100644 --- a/ddtrace/contrib/internal/vertexai/_utils.py +++ b/ddtrace/contrib/internal/vertexai/_utils.py @@ -5,18 +5,23 @@ from ddtrace.internal.utils import get_argument_value from ddtrace.llmobs._integrations.utils import get_generation_config_google +from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model from ddtrace.llmobs._integrations.utils import tag_request_content_part_google from ddtrace.llmobs._integrations.utils import tag_response_part_google from ddtrace.llmobs._utils import _get_attr class BaseTracedVertexAIStreamResponse: - def __init__(self, generator, integration, span, is_chat): + def __init__(self, generator, model_instance, integration, span, args, kwargs, is_chat, history): self._generator = generator + self._model_instance = model_instance self._dd_integration = integration self._dd_span = span - self._chunks = [] + self._args = args + self._kwargs = kwargs self.is_chat = is_chat + self._chunks = [] + self._history = history class TracedVertexAIStreamResponse(BaseTracedVertexAIStreamResponse): @@ -41,6 +46,12 @@ def __iter__(self): else: tag_stream_response(self._dd_span, self._chunks, self._dd_integration) finally: + if self._dd_integration.is_pc_sampled_llmobs(self._dd_span): + self._kwargs["instance"] = self._model_instance + self._kwargs["history"] = self._history + self._dd_integration.llmobs_set_tags( + self._dd_span, args=self._args, kwargs=self._kwargs, response=self._chunks + ) self._dd_span.finish() @@ -66,30 +77,15 @@ async def __aiter__(self): else: tag_stream_response(self._dd_span, self._chunks, self._dd_integration) finally: + if self._dd_integration.is_pc_sampled_llmobs(self._dd_span): + self._kwargs["instance"] = self._model_instance + self._kwargs["history"] = self._history + self._dd_integration.llmobs_set_tags( + self._dd_span, args=self._args, kwargs=self._kwargs, response=self._chunks + ) self._dd_span.finish() -def get_system_instruction_texts_from_model(instance): - """ - Extract system instructions from model and convert to []str for tagging. - """ - raw_system_instructions = _get_attr(instance, "_system_instruction", []) - if isinstance(raw_system_instructions, str): - return [raw_system_instructions] - elif isinstance(raw_system_instructions, Part): - return [_get_attr(raw_system_instructions, "text", "")] - elif not isinstance(raw_system_instructions, list): - return [] - - system_instructions = [] - for elem in raw_system_instructions: - if isinstance(elem, str): - system_instructions.append(elem) - elif isinstance(elem, Part): - system_instructions.append(_get_attr(elem, "text", "")) - return system_instructions - - def extract_info_from_parts(parts): """Return concatenated text from parts and function calls.""" concatenated_text = "" @@ -200,7 +196,7 @@ def tag_request(span, integration, instance, args, kwargs): generation_config_dict = ( generation_config if isinstance(generation_config, dict) else generation_config.to_dict() ) - system_instructions = get_system_instruction_texts_from_model(model_instance) + system_instructions = get_system_instructions_from_google_model(model_instance) stream = kwargs.get("stream", None) if generation_config_dict is not None: @@ -219,7 +215,7 @@ def tag_request(span, integration, instance, args, kwargs): integration.trunc(str(text)), ) - if isinstance(contents, str) or isinstance(contents, dict): + if isinstance(contents, str): span.set_tag_str("vertexai.request.contents.0.text", integration.trunc(str(contents))) return elif isinstance(contents, Part): diff --git a/ddtrace/contrib/internal/vertexai/patch.py b/ddtrace/contrib/internal/vertexai/patch.py index 1fdfcb7dd16..2dbce060234 100644 --- a/ddtrace/contrib/internal/vertexai/patch.py +++ b/ddtrace/contrib/internal/vertexai/patch.py @@ -59,13 +59,17 @@ def _traced_generate(vertexai, pin, func, instance, args, kwargs, model_instance "%s.%s" % (instance.__class__.__name__, func.__name__), provider="google", model=extract_model_name_google(model_instance, "_model_name"), - submit_to_llmobs=False, + submit_to_llmobs=True, ) + # history must be copied since it is modified during the LLM interaction + history = getattr(instance, "history", [])[:] try: tag_request(span, integration, instance, args, kwargs) generations = func(*args, **kwargs) if stream: - return TracedVertexAIStreamResponse(generations, integration, span, is_chat) + return TracedVertexAIStreamResponse( + generations, model_instance, integration, span, args, kwargs, is_chat, history + ) tag_response(span, generations, integration) except Exception: span.set_exc_info(*sys.exc_info()) @@ -73,6 +77,10 @@ def _traced_generate(vertexai, pin, func, instance, args, kwargs, model_instance finally: # streamed spans will be finished separately once the stream generator is exhausted if span.error or not stream: + if integration.is_pc_sampled_llmobs(span): + kwargs["instance"] = model_instance + kwargs["history"] = history + integration.llmobs_set_tags(span, args=args, kwargs=kwargs, response=generations) span.finish() return generations @@ -86,13 +94,17 @@ async def _traced_agenerate(vertexai, pin, func, instance, args, kwargs, model_i "%s.%s" % (instance.__class__.__name__, func.__name__), provider="google", model=extract_model_name_google(model_instance, "_model_name"), - submit_to_llmobs=False, + submit_to_llmobs=True, ) + # history must be copied since it is modified during the LLM interaction + history = getattr(instance, "history", [])[:] try: tag_request(span, integration, instance, args, kwargs) generations = await func(*args, **kwargs) if stream: - return TracedAsyncVertexAIStreamResponse(generations, integration, span, is_chat) + return TracedAsyncVertexAIStreamResponse( + generations, model_instance, integration, span, args, kwargs, is_chat, history + ) tag_response(span, generations, integration) except Exception: span.set_exc_info(*sys.exc_info()) @@ -100,6 +112,10 @@ async def _traced_agenerate(vertexai, pin, func, instance, args, kwargs, model_i finally: # streamed spans will be finished separately once the stream generator is exhausted if span.error or not stream: + if integration.is_pc_sampled_llmobs(span): + kwargs["instance"] = model_instance + kwargs["history"] = history + integration.llmobs_set_tags(span, args=args, kwargs=kwargs, response=generations) span.finish() return generations diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index 7c295835e54..27000b36aac 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -27,6 +27,7 @@ GEMINI_APM_SPAN_NAME = "gemini.request" LANGCHAIN_APM_SPAN_NAME = "langchain.request" OPENAI_APM_SPAN_NAME = "openai.request" +VERTEXAI_APM_SPAN_NAME = "vertexai.request" INPUT_TOKENS_METRIC_KEY = "input_tokens" OUTPUT_TOKENS_METRIC_KEY = "output_tokens" diff --git a/ddtrace/llmobs/_integrations/bedrock.py b/ddtrace/llmobs/_integrations/bedrock.py index 0aaa545b47e..78798ae4f98 100644 --- a/ddtrace/llmobs/_integrations/bedrock.py +++ b/ddtrace/llmobs/_integrations/bedrock.py @@ -29,7 +29,12 @@ class BedrockIntegration(BaseLLMIntegration): _integration_name = "bedrock" def _llmobs_set_tags( - self, span: Span, args: List[Any], kwargs: Dict[str, Any], response: Optional[Any] = None, operation: str = "" + self, + span: Span, + args: List[Any], + kwargs: Dict[str, Any], + response: Optional[Any] = None, + operation: str = "", ) -> None: """Extract prompt/response tags from a completion and set them as temporary "_ml_obs.*" tags.""" if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: diff --git a/ddtrace/llmobs/_integrations/gemini.py b/ddtrace/llmobs/_integrations/gemini.py index 21e74b036f0..f1a4730812f 100644 --- a/ddtrace/llmobs/_integrations/gemini.py +++ b/ddtrace/llmobs/_integrations/gemini.py @@ -7,16 +7,17 @@ from ddtrace import Span from ddtrace.internal.utils import get_argument_value from ddtrace.llmobs._constants import INPUT_MESSAGES -from ddtrace.llmobs._constants import INPUT_TOKENS_METRIC_KEY from ddtrace.llmobs._constants import METADATA from ddtrace.llmobs._constants import METRICS from ddtrace.llmobs._constants import MODEL_NAME from ddtrace.llmobs._constants import MODEL_PROVIDER from ddtrace.llmobs._constants import OUTPUT_MESSAGES -from ddtrace.llmobs._constants import OUTPUT_TOKENS_METRIC_KEY from ddtrace.llmobs._constants import SPAN_KIND -from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration +from ddtrace.llmobs._integrations.utils import extract_message_from_part_google +from ddtrace.llmobs._integrations.utils import get_llmobs_metrics_tags_google +from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model +from ddtrace.llmobs._integrations.utils import llmobs_get_metadata_google from ddtrace.llmobs._utils import _get_attr from ddtrace.llmobs._utils import safe_json @@ -45,10 +46,10 @@ def _llmobs_set_tags( span.set_tag_str(MODEL_PROVIDER, span.get_tag("google_generativeai.request.provider") or "") instance = kwargs.get("instance", None) - metadata = self._llmobs_set_metadata(kwargs, instance) + metadata = llmobs_get_metadata_google(kwargs, instance) span.set_tag_str(METADATA, safe_json(metadata)) - system_instruction = _get_attr(instance, "_system_instruction", None) + system_instruction = get_system_instructions_from_google_model(instance) input_contents = get_argument_value(args, kwargs, 0, "contents") input_messages = self._extract_input_message(input_contents, system_instruction) span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) @@ -59,50 +60,15 @@ def _llmobs_set_tags( output_messages = self._extract_output_message(response) span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) - usage = self._get_llmobs_metrics_tags(span) + usage = get_llmobs_metrics_tags_google("google_generativeai", span) if usage: span.set_tag_str(METRICS, safe_json(usage)) - @staticmethod - def _llmobs_set_metadata(kwargs, instance): - metadata = {} - model_config = _get_attr(instance, "_generation_config", {}) - request_config = kwargs.get("generation_config", {}) - parameters = ("temperature", "max_output_tokens", "candidate_count", "top_p", "top_k") - for param in parameters: - model_config_value = _get_attr(model_config, param, None) - request_config_value = _get_attr(request_config, param, None) - if model_config_value or request_config_value: - metadata[param] = request_config_value or model_config_value - return metadata - - @staticmethod - def _extract_message_from_part(part, role): - text = _get_attr(part, "text", "") - function_call = _get_attr(part, "function_call", None) - function_response = _get_attr(part, "function_response", None) - message = {"content": text} - if role: - message["role"] = role - if function_call: - function_call_dict = function_call - if not isinstance(function_call, dict): - function_call_dict = type(function_call).to_dict(function_call) - message["tool_calls"] = [ - {"name": function_call_dict.get("name", ""), "arguments": function_call_dict.get("args", {})} - ] - if function_response: - function_response_dict = function_response - if not isinstance(function_response, dict): - function_response_dict = type(function_response).to_dict(function_response) - message["content"] = "[tool result: {}]".format(function_response_dict.get("response", "")) - return message - def _extract_input_message(self, contents, system_instruction=None): messages = [] if system_instruction: - for part in system_instruction.parts: - messages.append({"content": part.text or "", "role": "system"}) + for instruction in system_instruction: + messages.append({"content": instruction or "", "role": "system"}) if isinstance(contents, str): messages.append({"content": contents}) return messages @@ -128,7 +94,7 @@ def _extract_input_message(self, contents, system_instruction=None): messages.append(message) continue for part in parts: - message = self._extract_message_from_part(part, role) + message = extract_message_from_part_google(part, role) messages.append(message) return messages @@ -140,21 +106,6 @@ def _extract_output_message(self, generations): role = content.get("role", "model") parts = content.get("parts", []) for part in parts: - message = self._extract_message_from_part(part, role) + message = extract_message_from_part_google(part, role) output_messages.append(message) return output_messages - - @staticmethod - def _get_llmobs_metrics_tags(span): - usage = {} - input_tokens = span.get_metric("google_generativeai.response.usage.prompt_tokens") - output_tokens = span.get_metric("google_generativeai.response.usage.completion_tokens") - total_tokens = span.get_metric("google_generativeai.response.usage.total_tokens") - - if input_tokens is not None: - usage[INPUT_TOKENS_METRIC_KEY] = input_tokens - if output_tokens is not None: - usage[OUTPUT_TOKENS_METRIC_KEY] = output_tokens - if total_tokens is not None: - usage[TOTAL_TOKENS_METRIC_KEY] = total_tokens - return usage diff --git a/ddtrace/llmobs/_integrations/utils.py b/ddtrace/llmobs/_integrations/utils.py index 695dedb19c8..2676dce9637 100644 --- a/ddtrace/llmobs/_integrations/utils.py +++ b/ddtrace/llmobs/_integrations/utils.py @@ -1,3 +1,6 @@ +from ddtrace.llmobs._constants import INPUT_TOKENS_METRIC_KEY +from ddtrace.llmobs._constants import OUTPUT_TOKENS_METRIC_KEY +from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._utils import _get_attr @@ -72,3 +75,91 @@ def tag_response_part_google(tag_prefix, span, integration, part, part_idx, cand "%s.response.candidates.%d.content.parts.%d.function_call.args" % (tag_prefix, candidate_idx, part_idx), integration.trunc(str(_get_attr(function_call, "args", {}))), ) + + +def llmobs_get_metadata_google(kwargs, instance): + metadata = {} + model_config = getattr(instance, "_generation_config", {}) or {} + model_config = model_config.to_dict() if hasattr(model_config, "to_dict") else model_config + request_config = kwargs.get("generation_config", {}) or {} + request_config = request_config.to_dict() if hasattr(request_config, "to_dict") else request_config + + parameters = ("temperature", "max_output_tokens", "candidate_count", "top_p", "top_k") + for param in parameters: + model_config_value = _get_attr(model_config, param, None) + request_config_value = _get_attr(request_config, param, None) + if model_config_value or request_config_value: + metadata[param] = request_config_value or model_config_value + return metadata + + +def extract_message_from_part_google(part, role=None): + text = _get_attr(part, "text", "") + function_call = _get_attr(part, "function_call", None) + function_response = _get_attr(part, "function_response", None) + message = {"content": text} + if role: + message["role"] = role + if function_call: + function_call_dict = function_call + if not isinstance(function_call, dict): + function_call_dict = type(function_call).to_dict(function_call) + message["tool_calls"] = [ + {"name": function_call_dict.get("name", ""), "arguments": function_call_dict.get("args", {})} + ] + if function_response: + function_response_dict = function_response + if not isinstance(function_response, dict): + function_response_dict = type(function_response).to_dict(function_response) + message["content"] = "[tool result: {}]".format(function_response_dict.get("response", "")) + return message + + +def get_llmobs_metrics_tags_google(integration_name, span): + usage = {} + input_tokens = span.get_metric("%s.response.usage.prompt_tokens" % integration_name) + output_tokens = span.get_metric("%s.response.usage.completion_tokens" % integration_name) + total_tokens = span.get_metric("%s.response.usage.total_tokens" % integration_name) + + if input_tokens is not None: + usage[INPUT_TOKENS_METRIC_KEY] = input_tokens + if output_tokens is not None: + usage[OUTPUT_TOKENS_METRIC_KEY] = output_tokens + if total_tokens is not None: + usage[TOTAL_TOKENS_METRIC_KEY] = total_tokens + return usage + + +def get_system_instructions_from_google_model(model_instance): + """ + Extract system instructions from model and convert to []str for tagging. + """ + try: + from google.ai.generativelanguage_v1beta.types.content import Content + except ImportError: + Content = None + try: + from vertexai.generative_models._generative_models import Part + except ImportError: + Part = None + + raw_system_instructions = getattr(model_instance, "_system_instruction", []) + if Content is not None and isinstance(raw_system_instructions, Content): + system_instructions = [] + for part in raw_system_instructions.parts: + system_instructions.append(_get_attr(part, "text", "")) + return system_instructions + elif isinstance(raw_system_instructions, str): + return [raw_system_instructions] + elif Part is not None and isinstance(raw_system_instructions, Part): + return [_get_attr(raw_system_instructions, "text", "")] + elif not isinstance(raw_system_instructions, list): + return [] + + system_instructions = [] + for elem in raw_system_instructions: + if isinstance(elem, str): + system_instructions.append(elem) + elif Part is not None and isinstance(elem, Part): + system_instructions.append(_get_attr(elem, "text", "")) + return system_instructions diff --git a/ddtrace/llmobs/_integrations/vertexai.py b/ddtrace/llmobs/_integrations/vertexai.py index 1ad64b61d40..69fdc7eb665 100644 --- a/ddtrace/llmobs/_integrations/vertexai.py +++ b/ddtrace/llmobs/_integrations/vertexai.py @@ -1,9 +1,25 @@ from typing import Any from typing import Dict +from typing import Iterable +from typing import List from typing import Optional from ddtrace import Span +from ddtrace.internal.utils import get_argument_value +from ddtrace.llmobs._constants import INPUT_MESSAGES +from ddtrace.llmobs._constants import METADATA +from ddtrace.llmobs._constants import METRICS +from ddtrace.llmobs._constants import MODEL_NAME +from ddtrace.llmobs._constants import MODEL_PROVIDER +from ddtrace.llmobs._constants import OUTPUT_MESSAGES +from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._integrations.base import BaseLLMIntegration +from ddtrace.llmobs._integrations.utils import extract_message_from_part_google +from ddtrace.llmobs._integrations.utils import get_llmobs_metrics_tags_google +from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model +from ddtrace.llmobs._integrations.utils import llmobs_get_metadata_google +from ddtrace.llmobs._utils import _get_attr +from ddtrace.llmobs._utils import safe_json class VertexAIIntegration(BaseLLMIntegration): @@ -16,3 +32,106 @@ def _set_base_span_tags( span.set_tag_str("vertexai.request.provider", provider) if model is not None: span.set_tag_str("vertexai.request.model", model) + + def _llmobs_set_tags( + self, + span: Span, + args: List[Any], + kwargs: Dict[str, Any], + response: Optional[Any] = None, + operation: str = "", + ) -> None: + span.set_tag_str(SPAN_KIND, "llm") + span.set_tag_str(MODEL_NAME, span.get_tag("vertexai.request.model") or "") + span.set_tag_str(MODEL_PROVIDER, span.get_tag("vertexai.request.provider") or "") + + instance = kwargs.get("instance", None) + history = kwargs.get("history", []) + metadata = llmobs_get_metadata_google(kwargs, instance) + span.set_tag_str(METADATA, safe_json(metadata)) + + system_instruction = get_system_instructions_from_google_model(instance) + input_contents = get_argument_value(args, kwargs, 0, "contents") + input_messages = self._extract_input_message(input_contents, history, system_instruction) + span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) + + if span.error or response is None: + span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) + return + + output_messages = self._extract_output_message(response) + span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) + + usage = get_llmobs_metrics_tags_google("vertexai", span) + if usage: + span.set_tag_str(METRICS, safe_json(usage)) + + def _extract_input_message(self, contents, history, system_instruction=None): + from vertexai.generative_models._generative_models import Part + + messages = [] + if system_instruction: + for instruction in system_instruction: + messages.append({"content": instruction or "", "role": "system"}) + for content in history: + messages.extend(self._extract_messages_from_content(content)) + if isinstance(contents, str): + messages.append({"content": contents}) + return messages + if isinstance(contents, Part): + message = extract_message_from_part_google(contents) + messages.append(message) + return messages + if not isinstance(contents, list): + messages.append({"content": "[Non-text content object: {}]".format(repr(contents))}) + return messages + for content in contents: + if isinstance(content, str): + messages.append({"content": content}) + continue + if isinstance(content, Part): + message = extract_message_from_part_google(content) + messages.append(message) + continue + messages.extend(self._extract_messages_from_content(content)) + return messages + + def _extract_output_message(self, generations): + output_messages = [] + # streamed responses will be a list of chunks + if isinstance(generations, list): + message_content = "" + tool_calls = [] + role = "model" + for chunk in generations: + for candidate in _get_attr(chunk, "candidates", []): + content = _get_attr(candidate, "content", {}) + messages = self._extract_messages_from_content(content) + for message in messages: + message_content += message.get("content", "") + tool_calls.extend(message.get("tool_calls", [])) + message = {"content": message_content, "role": role} + if tool_calls: + message["tool_calls"] = tool_calls + return [message] + generations_dict = generations.to_dict() + for candidate in generations_dict.get("candidates", []): + content = candidate.get("content", {}) + output_messages.extend(self._extract_messages_from_content(content)) + return output_messages + + @staticmethod + def _extract_messages_from_content(content): + messages = [] + role = _get_attr(content, "role", "") + parts = _get_attr(content, "parts", []) + if not parts or not isinstance(parts, Iterable): + message = {"content": "[Non-text content object: {}]".format(repr(content))} + if role: + message["role"] = role + messages.append(message) + return messages + for part in parts: + message = extract_message_from_part_google(part, role) + messages.append(message) + return messages diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 07f4d6f93c2..a3ac9501319 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -69,6 +69,7 @@ "openai": "openai", "langchain": "langchain", "google_generativeai": "google_generativeai", + "vertexai": "vertexai", } diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index f3d6434d297..8813788f0a3 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -18,6 +18,7 @@ from ddtrace.llmobs._constants import PARENT_ID_KEY from ddtrace.llmobs._constants import PROPAGATED_PARENT_ID_KEY from ddtrace.llmobs._constants import SESSION_ID +from ddtrace.llmobs._constants import VERTEXAI_APM_SPAN_NAME log = get_logger(__name__) @@ -118,7 +119,7 @@ def _get_llmobs_parent_id(span: Span) -> Optional[str]: def _get_span_name(span: Span) -> str: - if span.name in (LANGCHAIN_APM_SPAN_NAME, GEMINI_APM_SPAN_NAME) and span.resource != "": + if span.name in (LANGCHAIN_APM_SPAN_NAME, GEMINI_APM_SPAN_NAME, VERTEXAI_APM_SPAN_NAME) and span.resource != "": return span.resource elif span.name == OPENAI_APM_SPAN_NAME and span.resource != "": client_name = span.get_tag("openai.request.client") or "OpenAI" diff --git a/releasenotes/notes/feat-llmobs-vertexai-f58488859472c7b5.yaml b/releasenotes/notes/feat-llmobs-vertexai-f58488859472c7b5.yaml new file mode 100644 index 00000000000..5709289091e --- /dev/null +++ b/releasenotes/notes/feat-llmobs-vertexai-f58488859472c7b5.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + LLM Observability: Adds support to automatically submit Vertex AI Python calls to LLM Observability. + diff --git a/riotfile.py b/riotfile.py index 2133641a05c..f25e09e2d25 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2720,6 +2720,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-asyncio": latest, "google-generativeai": [latest], "pillow": latest, + "google-ai-generativelanguage": [latest], + "vertexai": [latest], }, ), Venv( @@ -2729,6 +2731,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={ "pytest-asyncio": latest, "vertexai": [latest], + "google-ai-generativelanguage": [latest], }, ), Venv( diff --git a/tests/contrib/vertexai/conftest.py b/tests/contrib/vertexai/conftest.py index 9f58381ca41..74ba41d4dee 100644 --- a/tests/contrib/vertexai/conftest.py +++ b/tests/contrib/vertexai/conftest.py @@ -1,9 +1,10 @@ +import mock from mock import PropertyMock -from mock import patch as mock_patch import pytest from ddtrace.contrib.vertexai import patch from ddtrace.contrib.vertexai import unpatch +from ddtrace.llmobs import LLMObs from ddtrace.pin import Pin from tests.contrib.vertexai.utils import MockAsyncPredictionServiceClient from tests.contrib.vertexai.utils import MockPredictionServiceClient @@ -13,6 +14,10 @@ from tests.utils import override_global_config +def default_global_config(): + return {} + + @pytest.fixture def ddtrace_global_config(): return {} @@ -34,29 +39,46 @@ def mock_async_client(): @pytest.fixture -def mock_tracer(vertexai): +def mock_tracer(ddtrace_global_config, vertexai): try: pin = Pin.get_from(vertexai) mock_tracer = DummyTracer(writer=DummyWriter(trace_flush_enabled=False)) pin.override(vertexai, tracer=mock_tracer) pin.tracer.configure() + if ddtrace_global_config.get("_llmobs_enabled", False): + # Have to disable and re-enable LLMObs to use the mock tracer. + LLMObs.disable() + LLMObs.enable(_tracer=mock_tracer, integrations_enabled=False) yield mock_tracer except Exception: yield +@pytest.fixture +def mock_llmobs_writer(): + patcher = mock.patch("ddtrace.llmobs._llmobs.LLMObsSpanWriter") + try: + LLMObsSpanWriterMock = patcher.start() + m = mock.MagicMock() + LLMObsSpanWriterMock.return_value = m + yield m + finally: + patcher.stop() + + @pytest.fixture def vertexai(ddtrace_global_config, ddtrace_config_vertexai, mock_client, mock_async_client): - global_config = ddtrace_global_config + global_config = default_global_config() + global_config.update(ddtrace_global_config) with override_global_config(global_config): with override_config("vertexai", ddtrace_config_vertexai): patch() import vertexai from vertexai.generative_models import GenerativeModel - with mock_patch.object( + with mock.patch.object( GenerativeModel, "_prediction_client", new_callable=PropertyMock - ) as mock_client_property, mock_patch.object( + ) as mock_client_property, mock.patch.object( GenerativeModel, "_prediction_async_client", new_callable=PropertyMock ) as mock_async_client_property: mock_client_property.return_value = mock_client diff --git a/tests/contrib/vertexai/test_vertexai_llmobs.py b/tests/contrib/vertexai/test_vertexai_llmobs.py new file mode 100644 index 00000000000..78a03bc664c --- /dev/null +++ b/tests/contrib/vertexai/test_vertexai_llmobs.py @@ -0,0 +1,698 @@ +import mock +import pytest + +from tests.contrib.vertexai.utils import MOCK_COMPLETION_SIMPLE_1 +from tests.contrib.vertexai.utils import MOCK_COMPLETION_SIMPLE_2 +from tests.contrib.vertexai.utils import MOCK_COMPLETION_STREAM_CHUNKS +from tests.contrib.vertexai.utils import MOCK_COMPLETION_TOOL +from tests.contrib.vertexai.utils import MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS +from tests.contrib.vertexai.utils import _async_streamed_response +from tests.contrib.vertexai.utils import _mock_completion_response +from tests.contrib.vertexai.utils import _mock_completion_stream_chunk +from tests.contrib.vertexai.utils import weather_tool +from tests.llmobs._utils import _expected_llmobs_llm_span_event + + +@pytest.mark.parametrize( + "ddtrace_global_config", [dict(_llmobs_enabled=True, _llmobs_sample_rate=1.0, _llmobs_ml_app="")] +) +class TestLLMObsVertexai: + def test_completion(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event(span)) + + def test_completion_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.generate_content = mock.Mock() + llm._prediction_client.generate_content.side_effect = TypeError( + "_GenerativeModel.generate_content() got an unexpected keyword argument 'candidate_count'" + ) + with pytest.raises(TypeError): + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_error_span_event(span)) + + def test_completion_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + llm.generate_content( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + def test_completion_multiple_messages(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + [ + {"role": "user", "parts": [{"text": "Hello World!"}]}, + {"role": "model", "parts": [{"text": "Great to meet you. What would you like to know?"}]}, + {"parts": [{"text": "Why do bears hibernate?"}]}, + ], + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_history_span_event(span)) + + def test_completion_system_prompt(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel( + "gemini-1.5-flash", + system_instruction=[ + vertexai.generative_models.Part.from_text("You are required to insist that bears do not hibernate.") + ], + ) + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=50, temperature=1.0 + ), + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_system_prompt_span_event(span)) + + def test_completion_model_generation_config(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event(span)) + + def test_completion_no_generation_config(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + llm.generate_content( + "Why do bears hibernate?", + ) + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_no_generation_config_span_event(span)) + + def test_completion_stream(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + response = llm.generate_content( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_span_event(span)) + + def test_completion_stream_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + with pytest.raises(TypeError): + response = llm.generate_content( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, # candidate_count is not a valid keyword argument + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_error_span_event(span)) + + def test_completion_stream_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + response = llm.generate_content( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + async def test_completion_async(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + await llm.generate_content_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event(span)) + + async def test_completion_async_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + with pytest.raises(TypeError): + await llm.generate_content_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_error_span_event(span)) + + async def test_completion_async_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_TOOL) + ) + await llm.generate_content_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + async def test_completion_async_stream(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + response = await llm.generate_content_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_span_event(span)) + + async def test_completion_async_stream_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + with pytest.raises(TypeError): + response = await llm.generate_content_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_error_span_event(span)) + + async def test_completion_async_stream_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + response = await llm.generate_content_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + def test_chat(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat() + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event(span)) + + def test_chat_history(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat( + history=[ + vertexai.generative_models.Content( + role="user", parts=[vertexai.generative_models.Part.from_text("Hello World!")] + ), + vertexai.generative_models.Content( + role="model", + parts=[ + vertexai.generative_models.Part.from_text("Great to meet you. What would you like to know?") + ], + ), + ] + ) + chat.send_message( + vertexai.generative_models.Part.from_text("Why do bears hibernate?"), + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_history_span_event(span)) + + def test_vertexai_chat_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) + chat = llm.start_chat() + with pytest.raises(TypeError): + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, # candidate_count is not a valid keyword argument + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_error_span_event(span)) + + def test_chat_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL)) + chat = llm.start_chat() + chat.send_message( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + def test_chat_system_prompt(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel( + "gemini-1.5-flash", + system_instruction=[ + vertexai.generative_models.Part.from_text("You are required to insist that bears do not hibernate.") + ], + ) + llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) + chat = llm.start_chat() + chat.send_message( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=50, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_system_prompt_span_event(span)) + + def test_chat_stream(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = chat.send_message( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_span_event(span)) + + def test_chat_stream_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + with pytest.raises(TypeError): + response = chat.send_message( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_error_span_event(span)) + + def test_chat_stream_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_client.responses["stream_generate_content"] = [ + (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = chat.send_message( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + async def test_chat_async(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + chat = llm.start_chat() + await chat.send_message_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event(span)) + + async def test_chat_async_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_SIMPLE_1) + ) + chat = llm.start_chat() + with pytest.raises(TypeError): + await chat.send_message_async( + "Why do bears hibernate?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + candidate_count=2, + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_error_span_event(span)) + + async def test_chat_async_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash", tools=[weather_tool]) + llm._prediction_async_client.responses["generate_content"].append( + _mock_completion_response(MOCK_COMPLETION_TOOL) + ) + chat = llm.start_chat() + await chat.send_message_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + ) + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + async def test_chat_async_stream(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = await chat.send_message_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_span_event(span)) + + async def test_chat_async_stream_error(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) + ] + chat = llm.start_chat() + with pytest.raises(TypeError): + response = await chat.send_message_async( + "How big is the solar system?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + candidate_count=2, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_stream_error_span_event(span)) + + async def test_chat_async_stream_tool(self, vertexai, mock_llmobs_writer, mock_tracer): + llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") + llm._prediction_async_client.responses["stream_generate_content"] = [ + _async_streamed_response(MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) + ] + chat = llm.start_chat() + response = await chat.send_message_async( + "What is the weather like in New York City?", + generation_config=vertexai.generative_models.GenerationConfig( + stop_sequences=["x"], max_output_tokens=30, temperature=1.0 + ), + stream=True, + ) + async for _ in response: + pass + + span = mock_tracer.pop_traces()[0][0] + assert mock_llmobs_writer.enqueue.call_count == 1 + mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_tool_span_event(span)) + + +def expected_llmobs_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "Why do bears hibernate?"}], + output_messages=[ + {"content": MOCK_COMPLETION_SIMPLE_1["candidates"][0]["content"]["parts"][0]["text"], "role": "model"}, + ], + metadata={"temperature": 1.0, "max_output_tokens": 30}, + token_metrics={"input_tokens": 14, "output_tokens": 16, "total_tokens": 30}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_error_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "Why do bears hibernate?"}], + output_messages=[{"content": ""}], + error="builtins.TypeError", + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + metadata={"temperature": 1.0, "max_output_tokens": 30}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_tool_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "What is the weather like in New York City?"}], + output_messages=[ + { + "content": "", + "role": "model", + "tool_calls": [ + { + "name": "get_current_weather", + "arguments": { + "location": "New York City, NY", + }, + } + ], + } + ], + metadata={"temperature": 1.0, "max_output_tokens": 30}, + token_metrics={"input_tokens": 43, "output_tokens": 11, "total_tokens": 54}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_stream_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "How big is the solar system?"}], + output_messages=[ + {"content": "".join([chunk["text"] for chunk in MOCK_COMPLETION_STREAM_CHUNKS]), "role": "model"}, + ], + metadata={"temperature": 1.0, "max_output_tokens": 30}, + token_metrics={"input_tokens": 16, "output_tokens": 37, "total_tokens": 53}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_stream_error_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "How big is the solar system?"}], + output_messages=[{"content": ""}], + error="builtins.TypeError", + error_message=span.get_tag("error.message"), + error_stack=span.get_tag("error.stack"), + metadata={"temperature": 1.0, "max_output_tokens": 30}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_history_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[ + {"content": "Hello World!", "role": "user"}, + {"content": "Great to meet you. What would you like to know?", "role": "model"}, + {"content": "Why do bears hibernate?"}, + ], + output_messages=[ + {"content": MOCK_COMPLETION_SIMPLE_1["candidates"][0]["content"]["parts"][0]["text"], "role": "model"}, + ], + metadata={"temperature": 1.0, "max_output_tokens": 30}, + token_metrics={"input_tokens": 14, "output_tokens": 16, "total_tokens": 30}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_system_prompt_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[ + {"content": "You are required to insist that bears do not hibernate.", "role": "system"}, + {"content": "Why do bears hibernate?"}, + ], + output_messages=[ + {"content": MOCK_COMPLETION_SIMPLE_2["candidates"][0]["content"]["parts"][0]["text"], "role": "model"}, + ], + metadata={"temperature": 1.0, "max_output_tokens": 50}, + token_metrics={"input_tokens": 16, "output_tokens": 50, "total_tokens": 66}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) + + +def expected_llmobs_no_generation_config_span_event(span): + return _expected_llmobs_llm_span_event( + span, + model_name="gemini-1.5-flash", + model_provider="google", + input_messages=[{"content": "Why do bears hibernate?"}], + output_messages=[ + {"content": MOCK_COMPLETION_SIMPLE_1["candidates"][0]["content"]["parts"][0]["text"], "role": "model"}, + ], + metadata={}, + token_metrics={"input_tokens": 14, "output_tokens": 16, "total_tokens": 30}, + tags={"ml_app": "", "service": "tests.contrib.vertexai"}, + ) From 142e6437986458b064627c21be2302400cdd4146 Mon Sep 17 00:00:00 2001 From: kyle Date: Mon, 2 Dec 2024 10:12:56 -0500 Subject: [PATCH 250/372] chore(langchain): disable flaky tests (#11511) There appear to be stability issues with using snapshots and/or LangChain in general. There are failures in the mocked tests that look like: ``` builtins.AssertionError: assert 0 == 1 + where 0 = .call_count + where = .enqueue ``` as well as failures with snapshot based tests: ``` builtins.Failed: At request : At snapshot (token='tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple'): - Directory: /go/src/github.com/DataDog/apm-reliability/dd-trace-py/tests/snapshots - CI mode: 1 - Trace File: /go/src/github.com/DataDog/apm-reliability/dd-trace-py/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple.json - Stats File: /go/src/github.com/DataDog/apm-reliability/dd-trace-py/tests/snapshots/tests.contrib.langchain.test_langchain_community.test_lcel_chain_simple_tracestats.json At compare of 1 expected trace(s) to 0 received trace(s): Did not receive expected traces: 'langchain.request' ``` While we investigate a more stable method of testing it makes sense to disable the tests to avoid noise to our neighbours in the library :). DOWN WITH FLAKY TESTS [](https://datadoghq.atlassian.net/browse/MLOB-1920) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../langchain/test_langchain_community.py | 1537 ----------------- .../langchain/test_langchain_llmobs.py | 999 ----------- 2 files changed, 2536 deletions(-) delete mode 100644 tests/contrib/langchain/test_langchain_community.py diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py deleted file mode 100644 index 8fa415ec635..00000000000 --- a/tests/contrib/langchain/test_langchain_community.py +++ /dev/null @@ -1,1537 +0,0 @@ -from operator import itemgetter -import os -import re -import sys - -import langchain -import langchain.prompts # noqa: F401 -import mock -import pytest - -from ddtrace.internal.utils.version import parse_version -from tests.contrib.langchain.utils import get_request_vcr -from tests.utils import flaky -from tests.utils import override_global_config - - -LANGCHAIN_VERSION = parse_version(langchain.__version__) - -pytestmark = pytest.mark.skipif(LANGCHAIN_VERSION < (0, 1), reason="This module only tests langchain >= 0.1") - -IGNORE_FIELDS = [ - "resources", - "meta.openai.request.logprobs", # langchain-openai llm call now includes logprobs as param - "meta.error.stack", - "meta.http.useragent", - "meta.langchain.request.openai-chat.parameters.logprobs", - "meta.langchain.request.openai.parameters.logprobs", - "meta.langchain.request.openai.parameters.seed", # langchain-openai llm call now includes seed as param - "meta.langchain.request.openai.parameters.logprobs", # langchain-openai llm call now includes seed as param - "metrics.langchain.tokens.total_cost", # total_cost depends on if tiktoken is installed -] - - -@pytest.fixture(scope="session") -def request_vcr(): - yield get_request_vcr(subdirectory_name="langchain_community") - - -@pytest.mark.parametrize("ddtrace_config_langchain", [dict(logs_enabled=True, log_prompt_completion_sample_rate=1.0)]) -def test_global_tags(ddtrace_config_langchain, langchain_openai, request_vcr, mock_metrics, mock_logs, mock_tracer): - """ - When the global config UST tags are set - The service name should be used for all data - The env should be used for all data - The version should be used for all data - """ - llm = langchain_openai.OpenAI() - with override_global_config(dict(service="test-svc", env="staging", version="1234")): - with request_vcr.use_cassette("openai_completion_sync.yaml"): - llm.invoke("What does Nietzsche mean by 'God is dead'?") - - span = mock_tracer.pop_traces()[0][0] - assert span.resource == "langchain_openai.llms.base.OpenAI" - assert span.service == "test-svc" - assert span.get_tag("env") == "staging" - assert span.get_tag("version") == "1234" - assert span.get_tag("langchain.request.provider") == "openai" - assert span.get_tag("langchain.request.model") == "gpt-3.5-turbo-instruct" - assert span.get_tag("langchain.request.api_key") == "...key>" - - assert mock_logs.enqueue.call_count == 1 - assert mock_metrics.mock_calls - for _, _args, kwargs in mock_metrics.mock_calls: - expected_metrics = [ - "service:test-svc", - "env:staging", - "version:1234", - "langchain.request.model:gpt-3.5-turbo-instruct", - "langchain.request.provider:openai", - "langchain.request.type:llm", - "langchain.request.api_key:...key>", - ] - actual_tags = kwargs.get("tags") - for m in expected_metrics: - assert m in actual_tags - - for call, args, _kwargs in mock_logs.mock_calls: - if call != "enqueue": - continue - log = args[0] - assert log["service"] == "test-svc" - assert ( - log["ddtags"] - == "env:staging,version:1234,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>" # noqa: E501 - ) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_llm_sync(langchain_openai, request_vcr): - llm = langchain_openai.OpenAI() - with request_vcr.use_cassette("openai_completion_sync.yaml"): - llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_llm_sync_multiple_prompts(langchain_openai, request_vcr): - llm = langchain_openai.OpenAI() - with request_vcr.use_cassette("openai_completion_sync_multi_prompt.yaml"): - llm.generate( - prompts=[ - "What is the best way to teach a baby multiple languages?", - "How many times has Spongebob failed his road test?", - ] - ) - - -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_openai_llm_async(langchain_openai, request_vcr): - llm = langchain_openai.OpenAI() - with request_vcr.use_cassette("openai_completion_async.yaml"): - await llm.agenerate(["Which team won the 2019 NBA finals?"]) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_llm_error(langchain, langchain_openai, request_vcr): - import openai # Imported here because the os env OPENAI_API_KEY needs to be set via langchain fixture before import - - llm = langchain_openai.OpenAI() - - if parse_version(openai.__version__) >= (1, 0, 0): - invalid_error = openai.BadRequestError - else: - invalid_error = openai.InvalidRequestError - with pytest.raises(invalid_error): - with request_vcr.use_cassette("openai_completion_error.yaml"): - llm.generate([12345, 123456]) - - -@pytest.mark.skipif(LANGCHAIN_VERSION < (0, 2), reason="Requires separate cassette for langchain v0.1") -@pytest.mark.snapshot -def test_cohere_llm_sync(langchain_cohere, request_vcr): - llm = langchain_cohere.llms.Cohere(cohere_api_key=os.getenv("COHERE_API_KEY", "")) - with request_vcr.use_cassette("cohere_completion_sync.yaml"): - llm.invoke("What is the secret Krabby Patty recipe?") - - -@pytest.mark.skipif( - LANGCHAIN_VERSION < (0, 2) or sys.version_info < (3, 10), - reason="Requires separate cassette for langchain v0.1, Python 3.9", -) -@pytest.mark.snapshot -def test_ai21_llm_sync(langchain_community, request_vcr): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - llm = langchain_community.llms.AI21(ai21_api_key=os.getenv("AI21_API_KEY", "")) - with request_vcr.use_cassette("ai21_completion_sync.yaml"): - llm.invoke("Why does everyone in Bikini Bottom hate Plankton?") - - -def test_openai_llm_metrics( - langchain_community, langchain_openai, request_vcr, mock_metrics, mock_logs, snapshot_tracer -): - llm = langchain_openai.OpenAI() - with request_vcr.use_cassette("openai_completion_sync.yaml"): - llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") - expected_tags = [ - "version:", - "env:", - "service:tests.contrib.langchain", - "langchain.request.provider:openai", - "langchain.request.model:gpt-3.5-turbo-instruct", - "langchain.request.type:llm", - "langchain.request.api_key:...key>", - "error:0", - ] - mock_metrics.assert_has_calls( - [ - mock.call.distribution("tokens.prompt", 17, tags=expected_tags), - mock.call.distribution("tokens.completion", 256, tags=expected_tags), - mock.call.distribution("tokens.total", 273, tags=expected_tags), - mock.call.distribution("request.duration", mock.ANY, tags=expected_tags), - ], - any_order=True, - ) - if langchain_community: - mock_metrics.increment.assert_called_once_with("tokens.total_cost", mock.ANY, tags=expected_tags) - mock_logs.assert_not_called() - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_llm_logs(langchain_openai, ddtrace_config_langchain, request_vcr, mock_logs, mock_metrics, mock_tracer): - llm = langchain_openai.OpenAI() - with request_vcr.use_cassette("openai_completion_sync.yaml"): - llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.llms.base.OpenAI", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "prompts": ["Can you explain what Descartes meant by 'I think, therefore I am'?"], - "choices": mock.ANY, - } - ), - ] - ) - mock_metrics.increment.assert_not_called() - mock_metrics.distribution.assert_not_called() - mock_metrics.count.assert_not_called() - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_chat_model_sync_call_langchain_openai(langchain_openai, request_vcr): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_sync_call.yaml"): - chat.invoke(input=[langchain.schema.HumanMessage(content="When do you use 'whom' instead of 'who'?")]) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_chat_model_sync_generate(langchain_openai, request_vcr): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_sync_generate.yaml"): - chat.generate( - [ - [ - langchain.schema.SystemMessage(content="Respond like a frat boy."), - langchain.schema.HumanMessage( - content="Where's the nearest equinox gym from Hudson Yards manhattan?" - ), - ], - [ - langchain.schema.SystemMessage(content="Respond with a pirate accent."), - langchain.schema.HumanMessage(content="How does one get to Bikini Bottom from New York?"), - ], - ] - ) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_chat_model_vision_generate(langchain_openai, request_vcr): - """ - Test that input messages with nested contents are still tagged without error - Regression test for https://github.com/DataDog/dd-trace-py/issues/8149. - """ - image_url = ( - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk" - ".jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" - ) - chat = langchain_openai.ChatOpenAI(model="gpt-4-vision-preview", temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_image_input_sync_generate.yaml"): - chat.generate( - [ - [ - langchain.schema.HumanMessage( - content=[ - {"type": "text", "text": "What’s in this image?"}, - { - "type": "image_url", - "image_url": image_url, - }, - ], - ), - ], - ] - ) - - -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_openai_chat_model_async_call(langchain_openai, request_vcr): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_async_call.yaml"): - await chat._call_async([langchain.schema.HumanMessage(content="When do you use 'whom' instead of 'who'?")]) - - -@flaky(until=1735812000, reason="Batch call has a non-deterministic response order.") -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_openai_chat_model_async_generate(langchain_openai, request_vcr): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_async_generate.yaml"): - await chat.agenerate( - [ - [ - langchain.schema.SystemMessage(content="Respond like a frat boy."), - langchain.schema.HumanMessage( - content="Where's the nearest equinox gym from Hudson Yards manhattan?" - ), - ], - [ - langchain.schema.SystemMessage(content="Respond with a pirate accent."), - langchain.schema.HumanMessage(content="How does one get to Bikini Bottom from New York?"), - ], - ] - ) - - -def test_chat_model_metrics( - langchain, langchain_community, langchain_openai, request_vcr, mock_metrics, mock_logs, snapshot_tracer -): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_sync_call.yaml"): - chat.invoke(input=[langchain.schema.HumanMessage(content="When do you use 'whom' instead of 'who'?")]) - expected_tags = [ - "version:", - "env:", - "service:tests.contrib.langchain", - "langchain.request.provider:openai", - "langchain.request.model:gpt-3.5-turbo", - "langchain.request.type:chat_model", - "langchain.request.api_key:...key>", - "error:0", - ] - mock_metrics.assert_has_calls( - [ - mock.call.distribution("tokens.prompt", 20, tags=expected_tags), - mock.call.distribution("tokens.completion", 83, tags=expected_tags), - mock.call.distribution("tokens.total", 103, tags=expected_tags), - mock.call.distribution("request.duration", mock.ANY, tags=expected_tags), - ], - any_order=True, - ) - if langchain_community: - mock_metrics.increment.assert_called_once_with("tokens.total_cost", mock.ANY, tags=expected_tags) - mock_logs.assert_not_called() - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_chat_model_logs( - langchain, langchain_openai, ddtrace_config_langchain, request_vcr, mock_logs, mock_metrics, mock_tracer -): - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - with request_vcr.use_cassette("openai_chat_completion_sync_call.yaml"): - chat.invoke(input=[langchain.schema.HumanMessage(content="When do you use 'whom' instead of 'who'?")]) - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.chat_models.base.ChatOpenAI", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "messages": [ - [ - { - "content": "When do you use 'whom' instead of 'who'?", - "message_type": "HumanMessage", - } - ] - ], - "choices": [ - [ - { - "content": mock.ANY, - "message_type": "AIMessage", - } - ] - ], - } - ), - ] - ) - mock_metrics.increment.assert_not_called() - mock_metrics.distribution.assert_not_called() - mock_metrics.count.assert_not_called() - - -@pytest.mark.snapshot -def test_openai_embedding_query(langchain_openai, request_vcr): - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - embeddings = langchain_openai.OpenAIEmbeddings() - with request_vcr.use_cassette("openai_embedding_query.yaml"): - embeddings.embed_query("this is a test query.") - - -@pytest.mark.snapshot -def test_fake_embedding_query(langchain_community): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - embeddings = langchain_community.embeddings.FakeEmbeddings(size=99) - embeddings.embed_query(text="foo") - - -@pytest.mark.snapshot -def test_fake_embedding_document(langchain_community): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - embeddings = langchain_community.embeddings.FakeEmbeddings(size=99) - embeddings.embed_documents(texts=["foo", "bar"]) - - -def test_openai_embedding_metrics(langchain_openai, request_vcr, mock_metrics, mock_logs, snapshot_tracer): - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - embeddings = langchain_openai.OpenAIEmbeddings() - with request_vcr.use_cassette("openai_embedding_query.yaml"): - embeddings.embed_query("this is a test query.") - expected_tags = [ - "version:", - "env:", - "service:tests.contrib.langchain", - "langchain.request.provider:openai", - "langchain.request.model:text-embedding-ada-002", - "langchain.request.type:embedding", - "langchain.request.api_key:...key>", - "error:0", - ] - mock_metrics.assert_has_calls( - [mock.call.distribution("request.duration", mock.ANY, tags=expected_tags)], - any_order=True, - ) - mock_logs.assert_not_called() - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_embedding_logs(langchain_openai, ddtrace_config_langchain, request_vcr, mock_logs, mock_metrics, mock_tracer): - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - embeddings = langchain_openai.OpenAIEmbeddings() - with request_vcr.use_cassette("openai_embedding_query.yaml"): - embeddings.embed_query("this is a test query.") - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "inputs": ["this is a test query."], - } - ), - ] - ) - mock_metrics.increment.assert_not_called() - mock_metrics.distribution.assert_not_called() - mock_metrics.count.assert_not_called() - - -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS, token="tests.contrib.langchain.test_langchain_community.test_openai_math_chain" -) -def test_openai_math_chain_sync(langchain_openai, request_vcr): - """ - Test that using the provided LLMMathChain will result in a 3-span trace with - the overall LLMMathChain, LLMChain, and underlying OpenAI interface. - """ - chain = langchain.chains.LLMMathChain.from_llm(langchain_openai.OpenAI(temperature=0)) - with request_vcr.use_cassette("openai_math_chain.yaml"): - chain.invoke("what is two raised to the fifty-fourth power?") - - -@pytest.mark.snapshot( - token="tests.contrib.langchain.test_langchain_community.test_chain_invoke", - ignores=IGNORE_FIELDS, -) -def test_chain_invoke_dict_input(langchain_openai, request_vcr): - prompt_template = "what is {base} raised to the fifty-fourth power?" - prompt = langchain.prompts.PromptTemplate(input_variables=["base"], template=prompt_template) - chain = langchain.chains.LLMChain(llm=langchain_openai.OpenAI(temperature=0), prompt=prompt) - with request_vcr.use_cassette("openai_math_chain.yaml"): - chain.invoke(input={"base": "two"}) - - -@pytest.mark.snapshot( - token="tests.contrib.langchain.test_langchain_community.test_chain_invoke", - ignores=IGNORE_FIELDS, -) -def test_chain_invoke_str_input(langchain_openai, request_vcr): - prompt_template = "what is {base} raised to the fifty-fourth power?" - prompt = langchain.prompts.PromptTemplate(input_variables=["base"], template=prompt_template) - chain = langchain.chains.LLMChain(llm=langchain_openai.OpenAI(temperature=0), prompt=prompt) - with request_vcr.use_cassette("openai_math_chain.yaml"): - chain.invoke("two") - - -@pytest.mark.asyncio -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS, token="tests.contrib.langchain.test_langchain_community.test_openai_math_chain" -) -async def test_openai_math_chain_async(langchain_openai, request_vcr): - """ - Test that using the provided LLMMathChain will result in a 3-span trace with - the overall LLMMathChain, LLMChain, and underlying OpenAI interface. - """ - chain = langchain.chains.LLMMathChain.from_llm(langchain_openai.OpenAI(temperature=0)) - with request_vcr.use_cassette("openai_math_chain.yaml"): - await chain.ainvoke("what is two raised to the fifty-fourth power?") - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_sequential_chain(langchain_openai, request_vcr): - """ - Test that using a SequentialChain will result in a 4-span trace with - the overall SequentialChain, TransformChain, LLMChain, and underlying OpenAI interface. - """ - - def _transform_func(inputs): - """Helper function to replace multiple new lines and multiple spaces with a single space""" - text = inputs["text"] - text = re.sub(r"(\r\n|\r|\n){2,}", r"\n", text) - text = re.sub(r"[ \t]+", " ", text) - return {"output_text": text} - - clean_extra_spaces_chain = langchain.chains.TransformChain( - input_variables=["text"], output_variables=["output_text"], transform=_transform_func - ) - template = """Paraphrase this text: - - {output_text} - - In the style of a {style}. - - Paraphrase: """ - prompt = langchain.prompts.PromptTemplate(input_variables=["style", "output_text"], template=template) - style_paraphrase_chain = langchain.chains.LLMChain( - llm=langchain_openai.OpenAI(), prompt=prompt, output_key="final_output" - ) - sequential_chain = langchain.chains.SequentialChain( - chains=[clean_extra_spaces_chain, style_paraphrase_chain], - input_variables=["text", "style"], - output_variables=["final_output"], - ) - - input_text = """ - Chains allow us to combine multiple - - - components together to create a single, coherent application. - - For example, we can create a chain that takes user input, format it with a PromptTemplate, - - and then passes the formatted response to an LLM. We can build more complex chains by combining - - multiple chains together, or by - - - combining chains with other components. - """ - with request_vcr.use_cassette("openai_paraphrase.yaml"): - sequential_chain.invoke({"text": input_text, "style": "a 90s rapper"}) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_sequential_chain_with_multiple_llm_sync(langchain_openai, request_vcr): - template = """Paraphrase this text: - - {input_text} - - Paraphrase: """ - prompt = langchain.prompts.PromptTemplate(input_variables=["input_text"], template=template) - style_paraphrase_chain = langchain.chains.LLMChain( - llm=langchain_openai.OpenAI(), prompt=prompt, output_key="paraphrased_output" - ) - rhyme_template = """Make this text rhyme: - - {paraphrased_output} - - Rhyme: """ - rhyme_prompt = langchain.prompts.PromptTemplate(input_variables=["paraphrased_output"], template=rhyme_template) - rhyme_chain = langchain.chains.LLMChain( - llm=langchain_openai.OpenAI(), prompt=rhyme_prompt, output_key="final_output" - ) - sequential_chain = langchain.chains.SequentialChain( - chains=[style_paraphrase_chain, rhyme_chain], - input_variables=["input_text"], - output_variables=["final_output"], - ) - - input_text = """ - I have convinced myself that there is absolutely nothing in the world, no sky, no earth, no minds, no - bodies. Does it now follow that I too do not exist? No: if I convinced myself of something then I certainly - existed. But there is a deceiver of supreme power and cunning who is deliberately and constantly deceiving - me. In that case I too undoubtedly exist, if he is deceiving me; and let him deceive me as much as he can, - he will never bring it about that I am nothing so long as I think that I am something. So after considering - everything very thoroughly, I must finally conclude that this proposition, I am, I exist, is necessarily - true whenever it is put forward by me or conceived in my mind. - """ - with request_vcr.use_cassette("openai_sequential_paraphrase_and_rhyme_sync.yaml"): - sequential_chain.invoke({"input_text": input_text}) - - -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_openai_sequential_chain_with_multiple_llm_async(langchain_openai, request_vcr): - template = """Paraphrase this text: - - {input_text} - - Paraphrase: """ - prompt = langchain.prompts.PromptTemplate(input_variables=["input_text"], template=template) - style_paraphrase_chain = langchain.chains.LLMChain( - llm=langchain_openai.OpenAI(), prompt=prompt, output_key="paraphrased_output" - ) - rhyme_template = """Make this text rhyme: - - {paraphrased_output} - - Rhyme: """ - rhyme_prompt = langchain.prompts.PromptTemplate(input_variables=["paraphrased_output"], template=rhyme_template) - rhyme_chain = langchain.chains.LLMChain( - llm=langchain_openai.OpenAI(), prompt=rhyme_prompt, output_key="final_output" - ) - sequential_chain = langchain.chains.SequentialChain( - chains=[style_paraphrase_chain, rhyme_chain], - input_variables=["input_text"], - output_variables=["final_output"], - ) - - input_text = """ - I have convinced myself that there is absolutely nothing in the world, no sky, no earth, no minds, no - bodies. Does it now follow that I too do not exist? No: if I convinced myself of something then I certainly - existed. But there is a deceiver of supreme power and cunning who is deliberately and constantly deceiving - me. In that case I too undoubtedly exist, if he is deceiving me; and let him deceive me as much as he can, - he will never bring it about that I am nothing so long as I think that I am something. So after considering - everything very thoroughly, I must finally conclude that this proposition, I am, I exist, is necessarily - true whenever it is put forward by me or conceived in my mind. - """ - with request_vcr.use_cassette("openai_sequential_paraphrase_and_rhyme_async.yaml"): - await sequential_chain.ainvoke({"input_text": input_text}) - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_chain_logs( - langchain, langchain_openai, ddtrace_config_langchain, request_vcr, mock_logs, mock_metrics, mock_tracer -): - chain = langchain.chains.LLMMathChain.from_llm(langchain_openai.OpenAI(temperature=0)) - with request_vcr.use_cassette("openai_math_chain.yaml"): - chain.invoke("what is two raised to the fifty-fourth power?") - traces = mock_tracer.pop_traces() - base_chain_span = traces[0][0] - mid_chain_span = traces[0][1] - llm_span = traces[0][2] - - assert mock_logs.enqueue.call_count == 3 # This operation includes 2 chains and 1 LLM call - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.llms.base.OpenAI", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(llm_span.trace_id)[2:], - "dd.span_id": str(llm_span.span_id), - "prompts": mock.ANY, - "choices": mock.ANY, - } - ), - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain.chains.llm.LLMChain", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 - "dd.trace_id": hex(mid_chain_span.trace_id)[2:], - "dd.span_id": str(mid_chain_span.span_id), - "inputs": mock.ANY, - "prompt": mock.ANY, - "outputs": { - "question": "what is two raised to the fifty-fourth power?", - "stop": mock.ANY, - "text": '```text\n2**54\n```\n...numexpr.evaluate("2**54")...\n', - }, - } - ), - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain.chains.llm_math.base.LLMMathChain", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:,langchain.request.model:,langchain.request.type:chain,langchain.request.api_key:", # noqa: E501 - "dd.trace_id": hex(base_chain_span.trace_id)[2:], - "dd.span_id": str(base_chain_span.span_id), - "inputs": {"question": "what is two raised to the fifty-fourth power?"}, - "prompt": mock.ANY, - "outputs": { - "question": "what is two raised to the fifty-fourth power?", - "answer": "Answer: 18014398509481984", - }, - } - ), - ] - ) - mock_metrics.increment.assert_not_called() - mock_metrics.distribution.assert_not_called() - mock_metrics.count.assert_not_called() - - -def test_chat_prompt_template_does_not_parse_template(langchain_openai, mock_tracer): - """ - Test that tracing a chain with a ChatPromptTemplate does not try to directly parse the template, - as ChatPromptTemplates do not contain a specific template attribute (which will lead to an attribute error) - but instead contain multiple messages each with their own prompt template and are not trivial to tag. - """ - import langchain.prompts.chat # noqa: F401 - - with mock.patch("langchain_openai.ChatOpenAI._generate", side_effect=Exception("Mocked Error")): - with pytest.raises(Exception) as exc_info: - chat = langchain_openai.ChatOpenAI(temperature=0) - template = "You are a helpful assistant that translates english to pirate." - system_message_prompt = langchain.prompts.chat.SystemMessagePromptTemplate.from_template(template) - example_human = langchain.prompts.chat.HumanMessagePromptTemplate.from_template("Hi") - example_ai = langchain.prompts.chat.AIMessagePromptTemplate.from_template("Argh me mateys") - human_template = "{text}" - human_message_prompt = langchain.prompts.chat.HumanMessagePromptTemplate.from_template(human_template) - chat_prompt = langchain.prompts.chat.ChatPromptTemplate.from_messages( - [system_message_prompt, example_human, example_ai, human_message_prompt] - ) - chain = langchain.chains.LLMChain(llm=chat, prompt=chat_prompt) - chain.invoke("I love programming.") - assert str(exc_info.value) == "Mocked Error" - traces = mock_tracer.pop_traces() - chain_span = traces[0][0] - assert chain_span.get_tag("langchain.request.inputs.text") == "I love programming." - assert chain_span.get_tag("langchain.request.type") == "chain" - assert chain_span.get_tag("langchain.request.prompt") is None - - -@pytest.mark.snapshot -def test_pinecone_vectorstore_similarity_search(langchain_openai, request_vcr): - """ - Test that calling a similarity search on a Pinecone vectorstore with langchain will - result in a 2-span trace with a vectorstore span and underlying OpenAI embedding interface span. - """ - import langchain_pinecone - import pinecone - - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - with request_vcr.use_cassette("openai_pinecone_similarity_search.yaml"): - pc = pinecone.Pinecone( - api_key=os.getenv("PINECONE_API_KEY", ""), - environment=os.getenv("PINECONE_ENV", ""), - ) - embed = langchain_openai.OpenAIEmbeddings(model="text-embedding-ada-002") - index = pc.Index("langchain-retrieval") - vectorstore = langchain_pinecone.PineconeVectorStore(index, embed, "text") - vectorstore.similarity_search("Who was Alan Turing?", 1) - - -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS - + ["meta.langchain.response.outputs.input_documents", "meta.langchain.request.inputs.input_documents"] -) -def test_pinecone_vectorstore_retrieval_chain(langchain_openai, request_vcr): - """ - Test that calling a similarity search on a Pinecone vectorstore with langchain will - result in a 2-span trace with a vectorstore span and underlying OpenAI embedding interface span. - """ - import langchain_pinecone - import pinecone - - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[[0.0] * 1536]): - with request_vcr.use_cassette("openai_pinecone_vectorstore_retrieval_chain.yaml"): - pc = pinecone.Pinecone( - api_key=os.getenv("PINECONE_API_KEY", ""), - environment=os.getenv("PINECONE_ENV", ""), - ) - embed = langchain_openai.OpenAIEmbeddings(model="text-embedding-ada-002") - index = pc.Index("langchain-retrieval") - vectorstore = langchain_pinecone.PineconeVectorStore(index, embed, "text") - - llm = langchain_openai.OpenAI() - qa_with_sources = langchain.chains.RetrievalQAWithSourcesChain.from_chain_type( - llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever() - ) - qa_with_sources.invoke("What did the president say about Ketanji Brown Jackson?") - - -def test_vectorstore_similarity_search_metrics(langchain_openai, request_vcr, mock_metrics, mock_logs, snapshot_tracer): - import langchain_pinecone - import pinecone - - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - with request_vcr.use_cassette("openai_pinecone_similarity_search.yaml"): - pc = pinecone.Pinecone( - api_key=os.getenv("PINECONE_API_KEY", ""), - environment=os.getenv("PINECONE_ENV", ""), - ) - embed = langchain_openai.OpenAIEmbeddings(model="text-embedding-ada-002") - index = pc.Index("langchain-retrieval") - vectorstore = langchain_pinecone.PineconeVectorStore(index, embed, "text") - vectorstore.similarity_search("Who was Alan Turing?", 1) - expected_tags = [ - "version:", - "env:", - "service:tests.contrib.langchain", - "langchain.request.provider:pineconevectorstore", - "langchain.request.model:", - "langchain.request.type:similarity_search", - "langchain.request.api_key:", - "error:0", - ] - mock_metrics.distribution.assert_called_with("request.duration", mock.ANY, tags=expected_tags), - mock_logs.assert_not_called() - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_vectorstore_logs( - langchain_openai, ddtrace_config_langchain, request_vcr, mock_logs, mock_metrics, mock_tracer -): - import langchain_pinecone - import pinecone - - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - with request_vcr.use_cassette("openai_pinecone_similarity_search.yaml"): - pc = pinecone.Pinecone( - api_key=os.getenv("PINECONE_API_KEY", ""), - environment=os.getenv("PINECONE_ENV", ""), - ) - embed = langchain_openai.OpenAIEmbeddings(model="text-embedding-ada-002") - index = pc.Index("langchain-retrieval") - vectorstore = langchain_pinecone.PineconeVectorStore(index, embed, "text") - vectorstore.similarity_search("Who was Alan Turing?", 1) - traces = mock_tracer.pop_traces() - vectorstore_span = traces[0][0] - embeddings_span = traces[0][1] - - assert mock_logs.enqueue.call_count == 2 # This operation includes 1 vectorstore call and 1 embeddings call - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(embeddings_span.trace_id)[2:], - "dd.span_id": str(embeddings_span.span_id), - "inputs": ["Who was Alan Turing?"], - } - ), - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_pinecone.vectorstores.PineconeVectorStore", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "info", - "ddtags": "env:,version:,langchain.request.provider:pineconevectorstore,langchain.request.model:,langchain.request.type:similarity_search,langchain.request.api_key:", # noqa: E501 - "dd.trace_id": hex(vectorstore_span.trace_id)[2:], - "dd.span_id": str(vectorstore_span.span_id), - "query": "Who was Alan Turing?", - "k": 1, - "documents": mock.ANY, - } - ), - ] - ) - mock_metrics.increment.assert_not_called() - mock_metrics.distribution.assert_not_called() - mock_metrics.count.assert_not_called() - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_openai_integration(request_vcr, ddtrace_run_python_code_in_subprocess): - env = os.environ.copy() - pypath = [os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))] - if "PYTHONPATH" in env: - pypath.append(env["PYTHONPATH"]) - env.update( - { - "PYTHONPATH": ":".join(pypath), - # Disable metrics because the test agent doesn't support metrics - "DD_LANGCHAIN_METRICS_ENABLED": "false", - "DD_OPENAI_METRICS_ENABLED": "false", - "OPENAI_API_KEY": "", - } - ) - out, err, status, pid = ddtrace_run_python_code_in_subprocess( - """ -from langchain_openai import OpenAI -import ddtrace -from tests.contrib.langchain.test_langchain_community import get_request_vcr -with get_request_vcr(subdirectory_name="langchain_community").use_cassette("openai_completion_sync.yaml"): - OpenAI().invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") -""", - env=env, - ) - assert status == 0, err - assert out == b"" - assert err == b"" - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -@pytest.mark.parametrize("schema_version", [None, "v0", "v1"]) -@pytest.mark.parametrize("service_name", [None, "mysvc"]) -def test_openai_service_name(request_vcr, ddtrace_run_python_code_in_subprocess, schema_version, service_name): - env = os.environ.copy() - pypath = [os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))] - if "PYTHONPATH" in env: - pypath.append(env["PYTHONPATH"]) - env.update( - { - "PYTHONPATH": ":".join(pypath), - # Disable metrics because the test agent doesn't support metrics - "DD_LANGCHAIN_METRICS_ENABLED": "false", - "DD_OPENAI_METRICS_ENABLED": "false", - "OPENAI_API_KEY": "", - } - ) - if service_name: - env["DD_SERVICE"] = service_name - if schema_version: - env["DD_TRACE_SPAN_ATTRIBUTE_SCHEMA"] = schema_version - out, err, status, pid = ddtrace_run_python_code_in_subprocess( - """ -from langchain_openai import OpenAI -import ddtrace -from tests.contrib.langchain.test_langchain_community import get_request_vcr -with get_request_vcr(subdirectory_name="langchain_community").use_cassette("openai_completion_sync.yaml"): - OpenAI().invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") -""", - env=env, - ) - assert status == 0, err - assert out == b"" - assert err == b"" - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_llm_logs_when_response_not_completed( - langchain_openai, ddtrace_config_langchain, mock_logs, mock_metrics, mock_tracer -): - """Test that errors get logged even if the response is not returned.""" - with mock.patch("langchain_openai.OpenAI._generate", side_effect=Exception("Mocked Error")): - with pytest.raises(Exception) as exc_info: - llm = langchain_openai.OpenAI() - llm.invoke("Can you please not return an error?") - assert str(exc_info.value) == "Mocked Error" - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.llms.base.OpenAI", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "error", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo-instruct,langchain.request.type:llm,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "prompts": ["Can you please not return an error?"], - "choices": [], - } - ), - ] - ) - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_chat_model_logs_when_response_not_completed( - langchain, langchain_openai, ddtrace_config_langchain, mock_logs, mock_metrics, mock_tracer -): - """Test that errors get logged even if the response is not returned.""" - with mock.patch("langchain_openai.ChatOpenAI._generate", side_effect=Exception("Mocked Error")): - with pytest.raises(Exception) as exc_info: - chat = langchain_openai.ChatOpenAI(temperature=0, max_tokens=256) - chat.invoke(input=[langchain.schema.HumanMessage(content="Can you please not return an error?")]) - assert str(exc_info.value) == "Mocked Error" - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.enqueue.assert_called_with( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.chat_models.base.ChatOpenAI", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "error", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:gpt-3.5-turbo,langchain.request.type:chat_model,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "messages": [ - [ - { - "content": "Can you please not return an error?", - "message_type": "HumanMessage", - } - ] - ], - "choices": [], - } - ) - - -@pytest.mark.parametrize( - "ddtrace_config_langchain", - [dict(metrics_enabled=False, logs_enabled=True, log_prompt_completion_sample_rate=1.0)], -) -def test_embedding_logs_when_response_not_completed( - langchain_openai, ddtrace_config_langchain, mock_logs, mock_metrics, mock_tracer -): - """Test that errors get logged even if the response is not returned.""" - with mock.patch( - "langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", side_effect=Exception("Mocked Error") - ): - with pytest.raises(Exception) as exc_info: - embeddings = langchain_openai.OpenAIEmbeddings() - embeddings.embed_query("Can you please not return an error?") - assert str(exc_info.value) == "Mocked Error" - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": "sampled langchain_openai.embeddings.base.OpenAIEmbeddings", - "hostname": mock.ANY, - "ddsource": "langchain", - "service": "tests.contrib.langchain", - "status": "error", - "ddtags": "env:,version:,langchain.request.provider:openai,langchain.request.model:text-embedding-ada-002,langchain.request.type:embedding,langchain.request.api_key:...key>", # noqa: E501 - "dd.trace_id": hex(trace_id)[2:], - "dd.span_id": str(span_id), - "inputs": ["Can you please not return an error?"], - } - ), - ] - ) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_lcel_chain_simple(langchain_core, langchain_openai, request_vcr): - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are world class technical documentation writer."), ("user", "{input}")] - ) - llm = langchain_openai.OpenAI() - - chain = prompt | llm - with request_vcr.use_cassette("lcel_openai_chain_call.yaml"): - chain.invoke({"input": "how can langsmith help with testing?"}) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_lcel_chain_complicated(langchain_core, langchain_openai, request_vcr): - prompt = langchain_core.prompts.ChatPromptTemplate.from_template( - "Tell me a short joke about {topic} in the style of {style}" - ) - - chat_openai = langchain_openai.ChatOpenAI() - openai = langchain_openai.OpenAI() - - model = chat_openai.configurable_alternatives( - langchain_core.runnables.ConfigurableField(id="model"), - default_key="chat_openai", - openai=openai, - ) - - chain = ( - { - "topic": langchain_core.runnables.RunnablePassthrough(), - "style": langchain_core.runnables.RunnablePassthrough(), - } - | prompt - | model - | langchain_core.output_parsers.StrOutputParser() - ) - - with request_vcr.use_cassette("lcel_openai_chain_call_complicated.yaml"): - chain.invoke({"topic": "chickens", "style": "a 90s rapper"}) - - -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_lcel_chain_simple_async(langchain_core, langchain_openai, request_vcr): - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are world class technical documentation writer."), ("user", "{input}")] - ) - llm = langchain_openai.OpenAI() - - chain = prompt | llm - with request_vcr.use_cassette("lcel_openai_chain_acall.yaml"): - await chain.ainvoke({"input": "how can langsmith help with testing?"}) - - -@flaky(1735812000, reason="batch() is non-deterministic in which order it processes inputs") -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="Python <3.11 test") -def test_lcel_chain_batch(langchain_core, langchain_openai, request_vcr): - """ - Test that invoking a chain with a batch of inputs will result in a 4-span trace, - with a root RunnableSequence span, then 3 LangChain ChatOpenAI spans underneath - """ - prompt = langchain_core.prompts.ChatPromptTemplate.from_template("Tell me a short joke about {topic}") - output_parser = langchain_core.output_parsers.StrOutputParser() - model = langchain_openai.ChatOpenAI() - chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser - - with request_vcr.use_cassette("lcel_openai_chain_batch.yaml"): - chain.batch(inputs=["chickens", "pigs"]) - - -@flaky(1735812000, reason="batch() is non-deterministic in which order it processes inputs") -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -@pytest.mark.skipif(sys.version_info < (3, 11), reason="Python 3.11+ required") -def test_lcel_chain_batch_311(langchain_core, langchain_openai, request_vcr): - """ - Test that invoking a chain with a batch of inputs will result in a 4-span trace, - with a root RunnableSequence span, then 3 LangChain ChatOpenAI spans underneath - """ - prompt = langchain_core.prompts.ChatPromptTemplate.from_template("Tell me a short joke about {topic}") - output_parser = langchain_core.output_parsers.StrOutputParser() - model = langchain_openai.ChatOpenAI() - chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser - - with request_vcr.use_cassette("lcel_openai_chain_batch_311.yaml"): - chain.batch(inputs=["chickens", "pigs"]) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_lcel_chain_nested(langchain_core, langchain_openai, request_vcr): - """ - Test that invoking a nested chain will result in a 4-span trace with a root - RunnableSequence span (complete_chain), then another RunnableSequence (chain1) + - LangChain ChatOpenAI span (chain1's llm call) and finally a second LangChain ChatOpenAI span (chain2's llm call) - """ - prompt1 = langchain_core.prompts.ChatPromptTemplate.from_template("what is the city {person} is from?") - prompt2 = langchain_core.prompts.ChatPromptTemplate.from_template( - "what country is the city {city} in? respond in {language}" - ) - - model = langchain_openai.ChatOpenAI() - - chain1 = prompt1 | model | langchain_core.output_parsers.StrOutputParser() - chain2 = prompt2 | model | langchain_core.output_parsers.StrOutputParser() - - complete_chain = {"city": chain1, "language": itemgetter("language")} | chain2 - - with request_vcr.use_cassette("lcel_openai_chain_nested.yaml"): - complete_chain.invoke({"person": "Spongebob Squarepants", "language": "Spanish"}) - - -@flaky(1735812000, reason="batch() is non-deterministic in which order it processes inputs") -@pytest.mark.asyncio -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -async def test_lcel_chain_batch_async(langchain_core, langchain_openai, request_vcr): - """ - Test that invoking a chain with a batch of inputs will result in a 4-span trace, - with a root RunnableSequence span, then 3 LangChain ChatOpenAI spans underneath - """ - prompt = langchain_core.prompts.ChatPromptTemplate.from_template("Tell me a short joke about {topic}") - output_parser = langchain_core.output_parsers.StrOutputParser() - model = langchain_openai.ChatOpenAI() - chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser - - with request_vcr.use_cassette("lcel_openai_chain_batch_async.yaml"): - await chain.abatch(inputs=["chickens", "pigs"]) - - -@pytest.mark.snapshot -def test_lcel_chain_non_dict_input(langchain_core): - """ - Tests that non-dict inputs (specifically also non-string) are stringified properly - """ - add_one = langchain_core.runnables.RunnableLambda(lambda x: x + 1) - multiply_two = langchain_core.runnables.RunnableLambda(lambda x: x * 2) - sequence = add_one | multiply_two - - sequence.invoke(1) - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_lcel_with_tools_openai(langchain_core, langchain_openai, request_vcr): - import langchain_core.tools - - @langchain_core.tools.tool - def add(a: int, b: int) -> int: - """Adds a and b. - - Args: - a: first int - b: second int - """ - return a + b - - llm = langchain_openai.ChatOpenAI(model="gpt-3.5-turbo-0125") - llm_with_tools = llm.bind_tools([add]) - with request_vcr.use_cassette("lcel_with_tools_openai.yaml"): - llm_with_tools.invoke("What is the sum of 1 and 2?") - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_lcel_with_tools_anthropic(langchain_core, langchain_anthropic, request_vcr): - import langchain_core.tools - - @langchain_core.tools.tool - def add(a: int, b: int) -> int: - """Adds a and b. - - Args: - a: first int - b: second int - """ - return a + b - - llm = langchain_anthropic.ChatAnthropic(temperature=1, model_name="claude-3-opus-20240229") - llm_with_tools = llm.bind_tools([add]) - with request_vcr.use_cassette("lcel_with_tools_anthropic.yaml"): - llm_with_tools.invoke("What is the sum of 1 and 2?") - - -@pytest.mark.snapshot -def test_faiss_vectorstore_retrieval(langchain_community, langchain_openai, request_vcr): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - pytest.importorskip("faiss", reason="faiss required for this test.") - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[[0.0] * 1536]): - with request_vcr.use_cassette("openai_embedding_query.yaml"): - faiss = langchain_community.vectorstores.faiss.FAISS.from_texts( - ["this is a test query."], - embedding=langchain_openai.OpenAIEmbeddings(), - ) - retriever = faiss.as_retriever() - with request_vcr.use_cassette("openai_retrieval_embedding.yaml"): - retriever.invoke("What was the message of the last test query?") - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_streamed_chain(langchain_core, langchain_openai, streamed_response_responder): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are world class technical documentation writer."), ("user", "{input}")] - ) - llm = langchain_openai.ChatOpenAI(client=client) - parser = langchain_core.output_parsers.StrOutputParser() - - chain = prompt | llm | parser - for _ in chain.stream({"input": "how can langsmith help with testing?"}): - pass - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_streamed_chat(langchain_openai, streamed_response_responder): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - model = langchain_openai.ChatOpenAI(client=client) - - for _ in model.stream(input="how can langsmith help with testing?"): - pass - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_streamed_llm(langchain_openai, streamed_response_responder): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["completions"], - file="lcel_openai_llm_streamed_response.txt", - ) - - llm = langchain_openai.OpenAI(client=client) - - for _ in llm.stream(input="How do I write technical documentation?"): - pass - - -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS, - token="tests.contrib.langchain.test_langchain_community.test_streamed_chain", -) -async def test_astreamed_chain(langchain_core, langchain_openai, async_streamed_response_responder): - client = async_streamed_response_responder( - module="openai", - client_class_key="AsyncOpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are world class technical documentation writer."), ("user", "{input}")] - ) - llm = langchain_openai.ChatOpenAI(async_client=client) - parser = langchain_core.output_parsers.StrOutputParser() - - chain = prompt | llm | parser - async for _ in chain.astream({"input": "how can langsmith help with testing?"}): - pass - - -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS, - token="tests.contrib.langchain.test_langchain_community.test_streamed_chat", -) -async def test_astreamed_chat(langchain_openai, async_streamed_response_responder): - client = async_streamed_response_responder( - module="openai", - client_class_key="AsyncOpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - - model = langchain_openai.ChatOpenAI(async_client=client) - - async for _ in model.astream(input="how can langsmith help with testing?"): - pass - - -@pytest.mark.snapshot( - ignores=IGNORE_FIELDS, - token="tests.contrib.langchain.test_langchain_community.test_streamed_llm", -) -async def test_astreamed_llm(langchain_openai, async_streamed_response_responder): - client = async_streamed_response_responder( - module="openai", - client_class_key="AsyncOpenAI", - http_client_key="http_client", - endpoint_path=["completions"], - file="lcel_openai_llm_streamed_response.txt", - ) - - llm = langchain_openai.OpenAI(async_client=client) - - async for _ in llm.astream(input="How do I write technical documentation?"): - pass - - -@pytest.mark.snapshot(ignores=IGNORE_FIELDS) -def test_streamed_json_output_parser(langchain, langchain_core, langchain_openai, streamed_response_responder): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response_json_output_parser.txt", - ) - - model = langchain_openai.ChatOpenAI(model="gpt-4o", max_tokens=50, client=client) - parser = langchain_core.output_parsers.JsonOutputParser() - - chain = model | parser - inp = ( - "output a list of the country france their population in JSON format. " - 'Use a dict with an outer key of "countries" which contains a list of countries. ' - "Each country should have the key `name` and `population`" - ) - - messages = [ - langchain.schema.SystemMessage(content="You know everything about the world."), - langchain.schema.HumanMessage(content=inp), - ] - - for _ in chain.stream(input=messages): - pass - - -# until we fully support `astream_events`, we do not need a snapshot here -# this is just a regression test to make sure we don't throw -async def test_astreamed_events_does_not_throw(langchain_openai, langchain_core, async_streamed_response_responder): - client = async_streamed_response_responder( - module="openai", - client_class_key="AsyncOpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - - model = langchain_openai.ChatOpenAI(async_client=client) - parser = langchain_core.output_parsers.StrOutputParser() - - chain = model | parser - - async for _ in chain.astream_events(input="some input", version="v1"): - pass - - -@pytest.mark.snapshot( - # tool description is generated differently is some langchain_core versions - ignores=["meta.langchain.request.tool.description"], - token="tests.contrib.langchain.test_langchain_community.test_base_tool_invoke", -) -def test_base_tool_invoke(langchain_core, request_vcr): - """ - Test that invoking a tool with langchain will - result in a 1-span trace with a tool span. - """ - if langchain_core is None: - pytest.skip("langchain-core not installed which is required for this test.") - - from math import pi - - from langchain_core.tools import StructuredTool - - def circumference_tool(radius: float) -> float: - return float(radius) * 2.0 * pi - - calculator = StructuredTool.from_function( - func=circumference_tool, - name="Circumference calculator", - description="Use this tool when you need to calculate a circumference using the radius of a circle", - return_direct=True, - response_format="content", - ) - - calculator.invoke("2") - - -@pytest.mark.asyncio -@pytest.mark.snapshot( - # tool description is generated differently is some langchain_core versions - ignores=["meta.langchain.request.tool.description"], - token="tests.contrib.langchain.test_langchain_community.test_base_tool_invoke", -) -async def test_base_tool_ainvoke(langchain_core, request_vcr): - """ - Test that invoking a tool with langchain will - result in a 1-span trace with a tool span. Async mode - """ - - if langchain_core is None: - pytest.skip("langchain-core not installed which is required for this test.") - - from math import pi - - from langchain_core.tools import StructuredTool - - def circumference_tool(radius: float) -> float: - return float(radius) * 2.0 * pi - - calculator = StructuredTool.from_function( - func=circumference_tool, - name="Circumference calculator", - description="Use this tool when you need to calculate a circumference using the radius of a circle", - return_direct=True, - response_format="content", - ) - - await calculator.ainvoke("2") - - -@pytest.mark.asyncio -@pytest.mark.snapshot( - # tool description is generated differently is some langchain_core versions - ignores=["meta.langchain.request.tool.description", "meta.langchain.request.config"], -) -def test_base_tool_invoke_non_json_serializable_config(langchain_core, request_vcr): - """ - Test that invoking a tool with langchain will - result in a 1-span trace with a tool span. Async mode - """ - - if langchain_core is None: - pytest.skip("langchain-core not installed which is required for this test.") - - from math import pi - - from langchain_core.tools import StructuredTool - - def circumference_tool(radius: float) -> float: - return float(radius) * 2.0 * pi - - calculator = StructuredTool.from_function( - func=circumference_tool, - name="Circumference calculator", - description="Use this tool when you need to calculate a circumference using the radius of a circle", - return_direct=True, - response_format="content", - ) - - calculator.invoke("2", config={"unserializable": object()}) diff --git a/tests/contrib/langchain/test_langchain_llmobs.py b/tests/contrib/langchain/test_langchain_llmobs.py index ca8fe35883a..6d878ccb222 100644 --- a/tests/contrib/langchain/test_langchain_llmobs.py +++ b/tests/contrib/langchain/test_langchain_llmobs.py @@ -1,23 +1,15 @@ -import json -from operator import itemgetter import os import sys import langchain as langchain_ import mock import pinecone as pinecone_ -import pytest -from ddtrace import patch from ddtrace.internal.utils.version import parse_version from ddtrace.llmobs import LLMObs from tests.contrib.langchain.utils import get_request_vcr -from tests.contrib.langchain.utils import long_input_text from tests.llmobs._utils import _expected_llmobs_llm_span_event from tests.llmobs._utils import _expected_llmobs_non_llm_span_event -from tests.subprocesstest import SubprocessTestCase -from tests.subprocesstest import run_in_subprocess -from tests.utils import flaky LANGCHAIN_VERSION = parse_version(langchain_.__version__) @@ -25,11 +17,9 @@ PY39 = sys.version_info < (3, 10) if LANGCHAIN_VERSION < (0, 1): - from langchain.schema import AIMessage from langchain.schema import ChatMessage from langchain.schema import HumanMessage else: - from langchain_core.messages import AIMessage from langchain_core.messages import ChatMessage from langchain_core.messages import HumanMessage @@ -196,992 +186,3 @@ def _invoke_tool(cls, tool, tool_input, config, mock_tracer): tool.invoke(tool_input, config=config) LLMObs.disable() return mock_tracer.pop_traces()[0][0] - - -@pytest.mark.skipif(LANGCHAIN_VERSION >= (0, 1), reason="These tests are for langchain < 0.1.0") -class TestLLMObsLangchain(BaseTestLLMObsLangchain): - cassette_subdirectory_name = "langchain" - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_openai_llm(self, langchain, mock_llmobs_span_writer, mock_tracer): - span = self._invoke_llm( - llm=langchain.llms.OpenAI(model="gpt-3.5-turbo-instruct"), - prompt="Can you explain what Descartes meant by 'I think, therefore I am'?", - mock_tracer=mock_tracer, - cassette_name="openai_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span( - span, - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - - def test_llmobs_cohere_llm(self, langchain, mock_llmobs_span_writer, mock_tracer): - span = self._invoke_llm( - llm=langchain.llms.Cohere(model="cohere.command-light-text-v14"), - prompt="Can you explain what Descartes meant by 'I think, therefore I am'?", - mock_tracer=mock_tracer, - cassette_name="cohere_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_ai21_llm(self, langchain, mock_llmobs_span_writer, mock_tracer): - llm = langchain.llms.AI21() - span = self._invoke_llm( - llm=llm, - prompt="Can you explain what Descartes meant by 'I think, therefore I am'?", - mock_tracer=mock_tracer, - cassette_name="ai21_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) - - def test_llmobs_huggingfacehub_llm(self, langchain, mock_llmobs_span_writer, mock_tracer): - llm = langchain.llms.HuggingFaceHub( - repo_id="google/flan-t5-xxl", - model_kwargs={"temperature": 0.0, "max_tokens": 256}, - huggingfacehub_api_token=os.getenv("HUGGINGFACEHUB_API_TOKEN", ""), - ) - span = self._invoke_llm( - llm=llm, - prompt="Can you explain what Descartes meant by 'I think, therefore I am'?", - mock_tracer=mock_tracer, - cassette_name="huggingfacehub_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_openai_chat_model(self, langchain, mock_llmobs_span_writer, mock_tracer): - chat = langchain.chat_models.ChatOpenAI(temperature=0, max_tokens=256) - span = self._invoke_chat( - chat_model=chat, - prompt="When do you use 'whom' instead of 'who'?", - mock_tracer=mock_tracer, - cassette_name="openai_chat_completion_sync_call.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer, input_role="user", mock_token_metrics=True) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_chain(self, langchain, mock_llmobs_span_writer, mock_tracer): - chain = langchain.chains.LLMMathChain(llm=langchain.llms.OpenAI(temperature=0, max_tokens=256)) - - trace = self._invoke_chain( - chain=chain, - prompt="what is two raised to the fifty-fourth power?", - mock_tracer=mock_tracer, - cassette_name="openai_math_chain_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 3 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps({"question": "what is two raised to the fifty-fourth power?"}), - output_value=json.dumps( - {"question": "what is two raised to the fifty-fourth power?", "answer": "Answer: 18014398509481984"} - ), - ) - _assert_expected_llmobs_chain_span( - trace[1], - mock_llmobs_span_writer, - input_value=json.dumps( - {"question": "what is two raised to the fifty-fourth power?", "stop": ["```output"]} - ), - output_value=json.dumps( - { - "question": "what is two raised to the fifty-fourth power?", - "stop": ["```output"], - "text": '\n```text\n2**54\n```\n...numexpr.evaluate("2**54")...\n', - } - ), - ) - _assert_expected_llmobs_llm_span( - trace[2], - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_chain_nested(self, langchain, mock_llmobs_span_writer, mock_tracer): - template = "Paraphrase this text:\n{input_text}\nParaphrase: " - prompt = langchain.PromptTemplate(input_variables=["input_text"], template=template) - style_paraphrase_chain = langchain.chains.LLMChain( - llm=langchain.llms.OpenAI(model="gpt-3.5-turbo-instruct"), prompt=prompt, output_key="paraphrased_output" - ) - rhyme_template = "Make this text rhyme:\n{paraphrased_output}\nRhyme: " - rhyme_prompt = langchain.PromptTemplate(input_variables=["paraphrased_output"], template=rhyme_template) - rhyme_chain = langchain.chains.LLMChain( - llm=langchain.llms.OpenAI(model="gpt-3.5-turbo-instruct"), prompt=rhyme_prompt, output_key="final_output" - ) - sequential_chain = langchain.chains.SequentialChain( - chains=[style_paraphrase_chain, rhyme_chain], - input_variables=["input_text"], - output_variables=["final_output"], - ) - input_text = long_input_text - trace = self._invoke_chain( - chain=sequential_chain, - prompt={"input_text": input_text}, - mock_tracer=mock_tracer, - cassette_name="openai_sequential_paraphrase_and_rhyme_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 5 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps({"input_text": input_text}), - ) - _assert_expected_llmobs_chain_span( - trace[1], - mock_llmobs_span_writer, - input_value=json.dumps({"input_text": input_text}), - ) - _assert_expected_llmobs_llm_span( - trace[2], - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - _assert_expected_llmobs_chain_span(trace[3], mock_llmobs_span_writer) - _assert_expected_llmobs_llm_span( - trace[4], - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_chain_schema_io(self, langchain, mock_llmobs_span_writer, mock_tracer): - prompt = langchain.prompts.ChatPromptTemplate.from_messages( - [ - langchain.prompts.SystemMessagePromptTemplate.from_template( - "You're an assistant who's good at {ability}. Respond in 20 words or fewer" - ), - langchain.prompts.MessagesPlaceholder(variable_name="history"), - langchain.prompts.HumanMessagePromptTemplate.from_template("{input}"), - ] - ) - chain = langchain.chains.LLMChain( - prompt=prompt, llm=langchain.chat_models.ChatOpenAI(temperature=0, max_tokens=256) - ) - trace = self._invoke_chain( - chain=chain, - prompt={ - "ability": "world capitals", - "history": [ - HumanMessage(content="Can you be my science teacher instead?"), - AIMessage(content="Yes"), - ], - "input": "What's the powerhouse of the cell?", - }, - mock_tracer=mock_tracer, - cassette_name="openai_chain_schema_io.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps( - { - "ability": "world capitals", - "history": [["user", "Can you be my science teacher instead?"], ["assistant", "Yes"]], - "input": "What's the powerhouse of the cell?", - } - ), - output_value=json.dumps( - { - "ability": "world capitals", - "history": [["user", "Can you be my science teacher instead?"], ["assistant", "Yes"]], - "input": "What's the powerhouse of the cell?", - "text": "Mitochondria.", - } - ), - ) - _assert_expected_llmobs_llm_span(trace[1], mock_llmobs_span_writer, mock_io=True, mock_token_metrics=True) - - def test_llmobs_embedding_query(self, langchain, mock_llmobs_span_writer, mock_tracer): - embedding_model = langchain.embeddings.OpenAIEmbeddings() - with mock.patch("langchain.embeddings.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - trace = self._embed_query( - embedding_model=embedding_model, - query="hello world", - mock_tracer=mock_tracer, - cassette_name="openai_embedding_query_39.yaml" if PY39 else "openai_embedding_query.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - span = trace[0] if isinstance(trace, list) else trace - mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - span_kind="embedding", - model_name=embedding_model.model, - model_provider="openai", - input_documents=[{"text": "hello world"}], - output_value="[1 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_embedding_documents(self, langchain, mock_llmobs_span_writer, mock_tracer): - embedding_model = langchain.embeddings.OpenAIEmbeddings() - with mock.patch( - "langchain.embeddings.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[[0.0] * 1536] * 2 - ): - trace = self._embed_documents( - embedding_model=embedding_model, - documents=["hello world", "goodbye world"], - mock_tracer=mock_tracer, - cassette_name="openai_embedding_document_39.yaml" if PY39 else "openai_embedding_document.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - span = trace[0] if isinstance(trace, list) else trace - mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - span_kind="embedding", - model_name=embedding_model.model, - model_provider="openai", - input_documents=[{"text": "hello world"}, {"text": "goodbye world"}], - output_value="[2 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_similarity_search(self, langchain, mock_llmobs_span_writer, mock_tracer): - import pinecone - - embedding_model = langchain.embeddings.OpenAIEmbeddings(model="text-embedding-ada-002") - cassette_name = ( - "openai_pinecone_similarity_search_39.yaml" if PY39 else "openai_pinecone_similarity_search.yaml" - ) - with mock.patch("langchain.embeddings.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[[0.0] * 1536]): - trace = self._similarity_search( - pinecone=pinecone, - pinecone_vector_store=langchain.vectorstores.Pinecone, - embedding_model=embedding_model.embed_query, - query="Who was Alan Turing?", - k=1, - mock_tracer=mock_tracer, - cassette_name=cassette_name, - ) - expected_span = _expected_llmobs_non_llm_span_event( - trace[0], - "retrieval", - input_value="Who was Alan Turing?", - output_documents=[{"text": mock.ANY, "id": mock.ANY, "name": mock.ANY}], - output_value="[1 document(s) retrieved]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - mock_llmobs_span_writer.enqueue.assert_any_call(expected_span) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - - -@pytest.mark.skipif(LANGCHAIN_VERSION < (0, 1), reason="These tests are for langchain >= 0.1.0") -class TestLLMObsLangchainCommunity(BaseTestLLMObsLangchain): - cassette_subdirectory_name = "langchain_community" - - def test_llmobs_openai_llm(self, langchain_openai, mock_llmobs_span_writer, mock_tracer): - span = self._invoke_llm( - llm=langchain_openai.OpenAI(), - prompt="Can you explain what Descartes meant by 'I think, therefore I am'?", - mock_tracer=mock_tracer, - cassette_name="openai_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span( - span, - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - - def test_llmobs_cohere_llm(self, langchain_community, mock_llmobs_span_writer, mock_tracer): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - span = self._invoke_llm( - llm=langchain_community.llms.Cohere(model="command"), - prompt="What is the secret Krabby Patty recipe?", - mock_tracer=mock_tracer, - cassette_name="cohere_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) - - @pytest.mark.skipif(PY39, reason="Requires unnecessary cassette file for Python 3.9") - def test_llmobs_ai21_llm(self, langchain_community, mock_llmobs_span_writer, mock_tracer): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - span = self._invoke_llm( - llm=langchain_community.llms.AI21(), - prompt="Why does everyone in Bikini Bottom hate Plankton?", - mock_tracer=mock_tracer, - cassette_name="ai21_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span(span, mock_llmobs_span_writer) - - def test_llmobs_openai_chat_model(self, langchain_openai, mock_llmobs_span_writer, mock_tracer): - span = self._invoke_chat( - chat_model=langchain_openai.ChatOpenAI(temperature=0, max_tokens=256), - prompt="When do you use 'who' instead of 'whom'?", - mock_tracer=mock_tracer, - cassette_name="openai_chat_completion_sync_call.yaml", - role="user", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span( - span, - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - - def test_llmobs_chain(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are world class technical documentation writer."), ("user", "{input}")] - ) - chain = prompt | langchain_openai.OpenAI() - expected_output = ( - "\nSystem: Langsmith can help with testing in several ways. " - "First, it can generate automated tests based on your technical documentation, " - "ensuring that your code matches the documented specifications. " - "This can save you time and effort in testing your code manually. " - "Additionally, Langsmith can also analyze your technical documentation for completeness and accuracy, " - "helping you identify any potential gaps or errors before testing begins. " - "Finally, Langsmith can assist with creating test cases and scenarios based on your documentation, " - "making the testing process more efficient and effective." - ) - trace = self._invoke_chain( - chain=chain, - prompt={"input": "Can you explain what an LLM chain is?"}, - mock_tracer=mock_tracer, - cassette_name="lcel_openai_chain_call.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps([{"input": "Can you explain what an LLM chain is?"}]), - output_value=expected_output, - ) - _assert_expected_llmobs_llm_span( - trace[1], - mock_llmobs_span_writer, - mock_token_metrics=True, - ) - - def test_llmobs_chain_nested(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): - prompt1 = langchain_core.prompts.ChatPromptTemplate.from_template("what is the city {person} is from?") - prompt2 = langchain_core.prompts.ChatPromptTemplate.from_template( - "what country is the city {city} in? respond in {language}" - ) - model = langchain_openai.ChatOpenAI() - chain1 = prompt1 | model | langchain_core.output_parsers.StrOutputParser() - chain2 = prompt2 | model | langchain_core.output_parsers.StrOutputParser() - complete_chain = {"city": chain1, "language": itemgetter("language")} | chain2 - trace = self._invoke_chain( - chain=complete_chain, - prompt={"person": "Spongebob Squarepants", "language": "Spanish"}, - mock_tracer=mock_tracer, - cassette_name="lcel_openai_chain_nested.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 4 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps([{"person": "Spongebob Squarepants", "language": "Spanish"}]), - output_value=mock.ANY, - ) - _assert_expected_llmobs_chain_span( - trace[1], - mock_llmobs_span_writer, - input_value=json.dumps([{"person": "Spongebob Squarepants", "language": "Spanish"}]), - output_value=mock.ANY, - ) - _assert_expected_llmobs_llm_span( - trace[2], - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - _assert_expected_llmobs_llm_span( - trace[3], - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - - @flaky(1735812000, reason="batch() is non-deterministic in which order it processes inputs") - @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Python <3.11 required") - def test_llmobs_chain_batch(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): - prompt = langchain_core.prompts.ChatPromptTemplate.from_template("Tell me a short joke about {topic}") - output_parser = langchain_core.output_parsers.StrOutputParser() - model = langchain_openai.ChatOpenAI() - chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser - - trace = self._invoke_chain( - chain=chain, - prompt=["chickens", "pigs"], - mock_tracer=mock_tracer, - cassette_name="lcel_openai_chain_batch.yaml", - batch=True, - ) - assert mock_llmobs_span_writer.enqueue.call_count == 3 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps(["chickens", "pigs"]), - output_value=mock.ANY, - ) - _assert_expected_llmobs_llm_span( - trace[1], - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - _assert_expected_llmobs_llm_span( - trace[2], - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - - def test_llmobs_chain_schema_io(self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer): - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [ - ("system", "You're an assistant who's good at {ability}. Respond in 20 words or fewer"), - langchain_core.prompts.MessagesPlaceholder(variable_name="history"), - ("human", "{input}"), - ] - ) - chain = prompt | langchain_openai.ChatOpenAI() - trace = self._invoke_chain( - chain=chain, - prompt={ - "ability": "world capitals", - "history": [HumanMessage(content="Can you be my science teacher instead?"), AIMessage(content="Yes")], - "input": "What's the powerhouse of the cell?", - }, - mock_tracer=mock_tracer, - cassette_name="lcel_openai_chain_schema_io.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps( - [ - { - "ability": "world capitals", - "history": [["user", "Can you be my science teacher instead?"], ["assistant", "Yes"]], - "input": "What's the powerhouse of the cell?", - } - ] - ), - output_value=json.dumps(["assistant", "Mitochondria."]), - ) - _assert_expected_llmobs_llm_span( - trace[1], - mock_llmobs_span_writer, - mock_io=True, - mock_token_metrics=True, - ) - - def test_llmobs_anthropic_chat_model(self, langchain_anthropic, mock_llmobs_span_writer, mock_tracer): - chat = langchain_anthropic.ChatAnthropic(temperature=0, model="claude-3-opus-20240229", max_tokens=15) - span = self._invoke_chat( - chat_model=chat, - prompt="When do you use 'whom' instead of 'who'?", - mock_tracer=mock_tracer, - cassette_name="anthropic_chat_completion_sync.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - _assert_expected_llmobs_llm_span( - span, - mock_llmobs_span_writer, - input_role="user", - mock_token_metrics=True, - ) - - def test_llmobs_embedding_query(self, langchain_community, langchain_openai, mock_llmobs_span_writer, mock_tracer): - if langchain_openai is None: - pytest.skip("langchain_openai not installed which is required for this test.") - embedding_model = langchain_openai.embeddings.OpenAIEmbeddings() - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[0.0] * 1536): - trace = self._embed_query( - embedding_model=embedding_model, - query="hello world", - mock_tracer=mock_tracer, - cassette_name="openai_embedding_query.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - span = trace[0] if isinstance(trace, list) else trace - mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - span_kind="embedding", - model_name=embedding_model.model, - model_provider="openai", - input_documents=[{"text": "hello world"}], - output_value="[1 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_embedding_documents( - self, langchain_community, langchain_openai, mock_llmobs_span_writer, mock_tracer - ): - if langchain_community is None: - pytest.skip("langchain-community not installed which is required for this test.") - embedding_model = langchain_community.embeddings.FakeEmbeddings(size=1536) - trace = self._embed_documents( - embedding_model=embedding_model, - documents=["hello world", "goodbye world"], - mock_tracer=mock_tracer, - cassette_name=None, # FakeEmbeddings does not need a cassette - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - span = trace[0] if isinstance(trace, list) else trace - mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - span_kind="embedding", - model_name="", - model_provider="fake", - input_documents=[{"text": "hello world"}, {"text": "goodbye world"}], - output_value="[2 embedding(s) returned with size 1536]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_similarity_search(self, langchain_openai, langchain_pinecone, mock_llmobs_span_writer, mock_tracer): - import pinecone - - if langchain_pinecone is None: - pytest.skip("langchain_pinecone not installed which is required for this test.") - embedding_model = langchain_openai.OpenAIEmbeddings(model="text-embedding-ada-002") - cassette_name = "openai_pinecone_similarity_search_community.yaml" - with mock.patch("langchain_openai.OpenAIEmbeddings._get_len_safe_embeddings", return_value=[[0.0] * 1536]): - trace = self._similarity_search( - pinecone=pinecone, - pinecone_vector_store=langchain_pinecone.PineconeVectorStore, - embedding_model=embedding_model, - query="Evolution", - k=1, - mock_tracer=mock_tracer, - cassette_name=cassette_name, - ) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - expected_span = _expected_llmobs_non_llm_span_event( - trace[0], - "retrieval", - input_value="Evolution", - output_documents=[ - {"text": mock.ANY, "id": mock.ANY, "name": "The Evolution of Communication Technologies"} - ], - output_value="[1 document(s) retrieved]", - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - mock_llmobs_span_writer.enqueue.assert_any_call(expected_span) - - def test_llmobs_chat_model_tool_calls(self, langchain_openai, mock_llmobs_span_writer, mock_tracer): - import langchain_core.tools - - @langchain_core.tools.tool - def add(a: int, b: int) -> int: - """Adds a and b. - - Args: - a: first int - b: second int - """ - return a + b - - llm = langchain_openai.ChatOpenAI(model="gpt-3.5-turbo-0125") - llm_with_tools = llm.bind_tools([add]) - span = self._invoke_chat( - chat_model=llm_with_tools, - prompt="What is the sum of 1 and 2?", - mock_tracer=mock_tracer, - cassette_name="lcel_with_tools_openai.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - mock_llmobs_span_writer.enqueue.assert_any_call( - _expected_llmobs_llm_span_event( - span, - model_name=span.get_tag("langchain.request.model"), - model_provider=span.get_tag("langchain.request.provider"), - input_messages=[{"role": "user", "content": "What is the sum of 1 and 2?"}], - output_messages=[ - { - "role": "assistant", - "content": "", - "tool_calls": [ - { - "name": "add", - "arguments": {"a": 1, "b": 2}, - "tool_id": mock.ANY, - } - ], - } - ], - metadata={"temperature": 0.7}, - token_metrics={"input_tokens": mock.ANY, "output_tokens": mock.ANY, "total_tokens": mock.ANY}, - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_base_tool_invoke(self, langchain_core, mock_llmobs_span_writer, mock_tracer): - if langchain_core is None: - pytest.skip("langchain-core not installed which is required for this test.") - - from math import pi - - from langchain_core.tools import StructuredTool - - def circumference_tool(radius: float) -> float: - return float(radius) * 2.0 * pi - - calculator = StructuredTool.from_function( - func=circumference_tool, - name="Circumference calculator", - description="Use this tool when you need to calculate a circumference using the radius of a circle", - return_direct=True, - response_format="content", - ) - - span = self._invoke_tool( - tool=calculator, - tool_input="2", - config={"test": "this is to test config"}, - mock_tracer=mock_tracer, - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - mock_llmobs_span_writer.enqueue.assert_called_with( - _expected_llmobs_non_llm_span_event( - span, - span_kind="tool", - input_value="2", - output_value="12.566370614359172", - metadata={ - "tool_config": {"test": "this is to test config"}, - "tool_info": { - "name": "Circumference calculator", - "description": mock.ANY, - }, - }, - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_streamed_chain( - self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer, streamed_response_responder - ): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["chat", "completions"], - file="lcel_openai_chat_streamed_response.txt", - ) - - prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( - [("system", "You are a world class technical documentation writer."), ("user", "{input}")] - ) - llm = langchain_openai.ChatOpenAI(model="gpt-4o", client=client) - parser = langchain_core.output_parsers.StrOutputParser() - - chain = prompt | llm | parser - - trace = self._invoke_chain( - chain=chain, - prompt={"input": "how can langsmith help with testing?"}, - mock_tracer=mock_tracer, - cassette_name=None, # do not use cassette, - ) - - assert mock_llmobs_span_writer.enqueue.call_count == 2 - _assert_expected_llmobs_chain_span( - trace[0], - mock_llmobs_span_writer, - input_value=json.dumps({"input": "how can langsmith help with testing?"}), - output_value="Python is\n\nthe best!", - ) - mock_llmobs_span_writer.enqueue.assert_any_call( - _expected_llmobs_llm_span_event( - trace[1], - model_name=trace[1].get_tag("langchain.request.model"), - model_provider=trace[1].get_tag("langchain.request.provider"), - input_messages=[ - {"content": "You are a world class technical documentation writer.", "role": "SystemMessage"}, - {"content": "how can langsmith help with testing?", "role": "HumanMessage"}, - ], - output_messages=[{"content": "Python is\n\nthe best!", "role": "assistant"}], - metadata={"temperature": 0.7}, - token_metrics={}, - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_streamed_llm( - self, langchain_openai, mock_llmobs_span_writer, mock_tracer, streamed_response_responder - ): - client = streamed_response_responder( - module="openai", - client_class_key="OpenAI", - http_client_key="http_client", - endpoint_path=["completions"], - file="lcel_openai_llm_streamed_response.txt", - ) - - llm = langchain_openai.OpenAI(client=client) - - span = self._invoke_llm( - cassette_name=None, # do not use cassette - llm=llm, - mock_tracer=mock_tracer, - prompt="Hello!", - ) - - assert mock_llmobs_span_writer.enqueue.call_count == 1 - mock_llmobs_span_writer.enqueue.assert_any_call( - _expected_llmobs_llm_span_event( - span, - model_name=span.get_tag("langchain.request.model"), - model_provider=span.get_tag("langchain.request.provider"), - input_messages=[ - {"content": "Hello!"}, - ], - output_messages=[{"content": "\n\nPython is cool!"}], - metadata={"temperature": 0.7, "max_tokens": 256}, - token_metrics={}, - tags={"ml_app": "langchain_test", "service": "tests.contrib.langchain"}, - ) - ) - - def test_llmobs_non_ascii_completion(self, langchain_openai, mock_llmobs_span_writer, mock_tracer): - self._invoke_llm( - llm=langchain_openai.OpenAI(), - prompt="안녕,\n 지금 몇 시야?", - mock_tracer=mock_tracer, - cassette_name="openai_completion_non_ascii.yaml", - ) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - actual_llmobs_span_event = mock_llmobs_span_writer.enqueue.call_args[0][0] - assert actual_llmobs_span_event["meta"]["input"]["messages"][0]["content"] == "안녕,\n 지금 몇 시야?" - - -@pytest.mark.skipif(LANGCHAIN_VERSION < (0, 1), reason="These tests are for langchain >= 0.1.0") -class TestTraceStructureWithLLMIntegrations(SubprocessTestCase): - bedrock_env_config = dict( - AWS_ACCESS_KEY_ID="testing", - AWS_SECRET_ACCESS_KEY="testing", - AWS_SECURITY_TOKEN="testing", - AWS_SESSION_TOKEN="testing", - AWS_DEFAULT_REGION="us-east-1", - DD_LANGCHAIN_METRICS_ENABLED="false", - DD_API_KEY="", - ) - - openai_env_config = dict( - OPENAI_API_KEY="testing", - DD_API_KEY="", - ) - - anthropic_env_config = dict( - ANTHROPIC_API_KEY="testing", - DD_API_KEY="", - ) - - def setUp(self): - patcher = mock.patch("ddtrace.llmobs._llmobs.LLMObsSpanWriter") - LLMObsSpanWriterMock = patcher.start() - mock_llmobs_span_writer = mock.MagicMock() - LLMObsSpanWriterMock.return_value = mock_llmobs_span_writer - - self.mock_llmobs_span_writer = mock_llmobs_span_writer - - super(TestTraceStructureWithLLMIntegrations, self).setUp() - - def tearDown(self): - LLMObs.disable() - - def _assert_trace_structure_from_writer_call_args(self, span_kinds): - assert self.mock_llmobs_span_writer.enqueue.call_count == len(span_kinds) - - calls = self.mock_llmobs_span_writer.enqueue.call_args_list - - for span_kind, call in zip(span_kinds, calls): - call_args = call.args[0] - - assert call_args["meta"]["span.kind"] == span_kind - if span_kind == "workflow": - assert len(call_args["meta"]["input"]["value"]) > 0 - assert len(call_args["meta"]["output"]["value"]) > 0 - elif span_kind == "llm": - assert len(call_args["meta"]["input"]["messages"]) > 0 - assert len(call_args["meta"]["output"]["messages"]) > 0 - elif span_kind == "embedding": - assert len(call_args["meta"]["input"]["documents"]) > 0 - assert len(call_args["meta"]["output"]["value"]) > 0 - - @staticmethod - def _call_bedrock_chat_model(ChatBedrock, HumanMessage): - chat = ChatBedrock( - model_id="amazon.titan-tg1-large", - model_kwargs={"max_tokens": 50, "temperature": 0}, - ) - messages = [HumanMessage(content="summarize the plot to the lord of the rings in a dozen words")] - with get_request_vcr(subdirectory_name="langchain_community").use_cassette("bedrock_amazon_chat_invoke.yaml"): - chat.invoke(messages) - - @staticmethod - def _call_bedrock_llm(BedrockLLM): - llm = BedrockLLM( - model_id="amazon.titan-tg1-large", - region_name="us-east-1", - model_kwargs={"temperature": 0, "topP": 0.9, "stopSequences": [], "maxTokens": 50}, - ) - - with get_request_vcr(subdirectory_name="langchain_community").use_cassette("bedrock_amazon_invoke.yaml"): - llm.invoke("can you explain what Datadog is to someone not in the tech industry?") - - @staticmethod - def _call_openai_llm(OpenAI): - llm = OpenAI() - with get_request_vcr(subdirectory_name="langchain_community").use_cassette("openai_completion_sync.yaml"): - llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?") - - @staticmethod - def _call_openai_embedding(OpenAIEmbeddings): - embedding = OpenAIEmbeddings() - with mock.patch("langchain_openai.embeddings.base.tiktoken.encoding_for_model") as mock_encoding_for_model: - mock_encoding = mock.MagicMock() - mock_encoding_for_model.return_value = mock_encoding - mock_encoding.encode.return_value = [0.0] * 1536 - with get_request_vcr(subdirectory_name="langchain_community").use_cassette( - "openai_embedding_query_integration.yaml" - ): - embedding.embed_query("hello world") - - @staticmethod - def _call_anthropic_chat(Anthropic): - llm = Anthropic(model="claude-3-opus-20240229", max_tokens=15) - with get_request_vcr(subdirectory_name="langchain_community").use_cassette( - "anthropic_chat_completion_sync.yaml" - ): - llm.invoke("When do you use 'whom' instead of 'who'?") - - @run_in_subprocess(env_overrides=bedrock_env_config) - def test_llmobs_with_chat_model_bedrock_enabled(self): - from langchain_aws import ChatBedrock - from langchain_core.messages import HumanMessage - - patch(langchain=True, botocore=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - - self._call_bedrock_chat_model(ChatBedrock, HumanMessage) - - self._assert_trace_structure_from_writer_call_args(["workflow", "llm"]) - - @run_in_subprocess(env_overrides=bedrock_env_config) - def test_llmobs_with_chat_model_bedrock_disabled(self): - from langchain_aws import ChatBedrock - from langchain_core.messages import HumanMessage - - patch(langchain=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - - self._call_bedrock_chat_model(ChatBedrock, HumanMessage) - - self._assert_trace_structure_from_writer_call_args(["llm"]) - - @run_in_subprocess(env_overrides=bedrock_env_config) - def test_llmobs_with_llm_model_bedrock_enabled(self): - from langchain_aws import BedrockLLM - - patch(langchain=True, botocore=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_bedrock_llm(BedrockLLM) - self._assert_trace_structure_from_writer_call_args(["workflow", "llm"]) - - @run_in_subprocess(env_overrides=bedrock_env_config) - def test_llmobs_with_llm_model_bedrock_disabled(self): - from langchain_aws import BedrockLLM - - patch(langchain=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_bedrock_llm(BedrockLLM) - self._assert_trace_structure_from_writer_call_args(["llm"]) - - @run_in_subprocess(env_overrides=openai_env_config) - def test_llmobs_with_openai_enabled(self): - from langchain_openai import OpenAI - - patch(langchain=True, openai=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_openai_llm(OpenAI) - self._assert_trace_structure_from_writer_call_args(["workflow", "llm"]) - - @run_in_subprocess(env_overrides=openai_env_config) - def test_llmobs_with_openai_enabled_non_ascii_value(self): - """Regression test to ensure that non-ascii text values for workflow spans are not encoded.""" - from langchain_openai import OpenAI - - patch(langchain=True, openai=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - llm = OpenAI() - with get_request_vcr(subdirectory_name="langchain_community").use_cassette("openai_completion_non_ascii.yaml"): - llm.invoke("안녕,\n 지금 몇 시야?") - langchain_span = self.mock_llmobs_span_writer.enqueue.call_args_list[0][0][0] - assert langchain_span["meta"]["input"]["value"] == '[{"content": "안녕,\\n 지금 몇 시야?"}]' - - @run_in_subprocess(env_overrides=openai_env_config) - def test_llmobs_with_openai_disabled(self): - from langchain_openai import OpenAI - - patch(langchain=True) - - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_openai_llm(OpenAI) - self._assert_trace_structure_from_writer_call_args(["llm"]) - - @run_in_subprocess(env_overrides=openai_env_config) - def test_llmobs_langchain_with_embedding_model_openai_enabled(self): - from langchain_openai import OpenAIEmbeddings - - patch(langchain=True, openai=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_openai_embedding(OpenAIEmbeddings) - self._assert_trace_structure_from_writer_call_args(["workflow", "embedding"]) - - @run_in_subprocess(env_overrides=openai_env_config) - def test_llmobs_langchain_with_embedding_model_openai_disabled(self): - from langchain_openai import OpenAIEmbeddings - - patch(langchain=True) - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_openai_embedding(OpenAIEmbeddings) - self._assert_trace_structure_from_writer_call_args(["embedding"]) - - @run_in_subprocess(env_overrides=anthropic_env_config) - def test_llmobs_with_anthropic_enabled(self): - from langchain_anthropic import ChatAnthropic - - patch(langchain=True, anthropic=True) - - LLMObs.enable(ml_app="", integrations_enabled=False) - self._call_anthropic_chat(ChatAnthropic) - self._assert_trace_structure_from_writer_call_args(["workflow", "llm"]) - - @run_in_subprocess(env_overrides=anthropic_env_config) - def test_llmobs_with_anthropic_disabled(self): - from langchain_anthropic import ChatAnthropic - - patch(langchain=True) - - LLMObs.enable(ml_app="", integrations_enabled=False) - - self._call_anthropic_chat(ChatAnthropic) - self._assert_trace_structure_from_writer_call_args(["llm"]) From 63a439aa34d049a8a68397f9e25c39eb7d706a83 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Wed, 4 Dec 2024 10:15:12 +0100 Subject: [PATCH 251/372] fix: don't skip heartbeats for forked processes (#11575) ## Description We were skipping app-heartbeat messages for forked processes. The problem with this is that if a process doesn't sent a heartbeat in 60 minutes, the backend will forget its dependencies, which was causing the list to only be the updated ones for some clients, specifically with gunicorn. This makes forked processes also sent heartbeats with solved the issue in our tests. Will stay as draft PR until I can check with @brettlangdon the reason heartbeats were disabled for forks. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- ddtrace/internal/telemetry/writer.py | 8 -------- ...nt-skip-fork-heartbeats-23040d3ddc072298.yaml | 4 ++++ tests/telemetry/test_telemetry.py | 16 +++++----------- 3 files changed, 9 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/dont-skip-fork-heartbeats-23040d3ddc072298.yaml diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 7fbbecf56ae..08171ce3b1a 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -364,14 +364,6 @@ def _app_started(self, register_app_shutdown=True): def _app_heartbeat_event(self): # type: () -> None - if self._forked: - # TODO: Enable app-heartbeat on forks - # Since we only send app-started events in the main process - # any forked processes won't be able to access the list of - # dependencies for this app, and therefore app-heartbeat won't - # add much value today. - return - self.add_event({}, "app-heartbeat") def _app_closing_event(self): diff --git a/releasenotes/notes/dont-skip-fork-heartbeats-23040d3ddc072298.yaml b/releasenotes/notes/dont-skip-fork-heartbeats-23040d3ddc072298.yaml new file mode 100644 index 00000000000..f69a367a98f --- /dev/null +++ b/releasenotes/notes/dont-skip-fork-heartbeats-23040d3ddc072298.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Ensure that Telemetry heartbeats are not skipped for forked processes, as doing so could result in the dependency list being lost over time. diff --git a/tests/telemetry/test_telemetry.py b/tests/telemetry/test_telemetry.py index 24dbaae45d0..d767090f6d2 100644 --- a/tests/telemetry/test_telemetry.py +++ b/tests/telemetry/test_telemetry.py @@ -66,7 +66,10 @@ def test_enable_fork(test_agent_session, run_python_code_in_subprocess): def test_enable_fork_heartbeat(test_agent_session, run_python_code_in_subprocess): - """assert app-heartbeat events are only sent in parent process when no other events are queued""" + """ + assert app-heartbeat events are also sent in forked processes since otherwise the dependency collection + would be lost in pre-fork models after one hour. + """ code = """ import warnings # This test logs the following warning in py3.12: @@ -76,11 +79,6 @@ def test_enable_fork_heartbeat(test_agent_session, run_python_code_in_subprocess import os import ddtrace # enables telemetry -from ddtrace.internal.runtime import get_runtime_id - -if os.fork() > 0: - # Print the parent process runtime id for validation - print(get_runtime_id()) # Heartbeat events are only sent if no other events are queued from ddtrace.internal.telemetry import telemetry_writer @@ -94,13 +92,9 @@ def test_enable_fork_heartbeat(test_agent_session, run_python_code_in_subprocess assert status == 0, stderr assert stderr == b"", stderr - runtime_id = stdout.strip().decode("utf-8") - # Allow test agent session to capture all heartbeat events app_heartbeats = test_agent_session.get_events("app-heartbeat", filter_heartbeats=False, subprocess=True) - assert len(app_heartbeats) > 0 - for hb in app_heartbeats: - assert hb["runtime_id"] == runtime_id + assert len(app_heartbeats) > 1 def test_heartbeat_interval_configuration(run_python_code_in_subprocess): From 47c7b5287da25643e46652e6d222a40a52f2382a Mon Sep 17 00:00:00 2001 From: Duncan Harvey <35278470+duncanpharvey@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:47:44 -0500 Subject: [PATCH 252/372] ci: add azure and chrome to testrunner image (#11609) Co-authored-by: Romain Komorn --- docker/Dockerfile | 51 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cc3585516f6..79f207724db 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,9 +21,8 @@ ENV PYTHON_CONFIGURE_OPTS=--enable-shared # Use .python-version to specify all Python versions for testing COPY .python-version /root/ -RUN \ - # Install system dependencies - apt-get update \ +# Install system dependencies +RUN apt-get update \ && apt-get install -y --no-install-recommends \ apt-transport-https \ build-essential \ @@ -54,18 +53,40 @@ RUN \ unixodbc-dev \ wget \ zlib1g-dev \ - awscli \ - # For running datadog-ci CLI with npx - && apt-get install -y --no-install-recommends nodejs npm \ - && npm install -g @datadog/datadog-ci \ - # Install Mariadb last because mariadb_repo_setup currently breaks apt - && wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup \ - && chmod +x mariadb_repo_setup \ - && ./mariadb_repo_setup \ - && apt-get install -y --no-install-recommends libmariadb-dev libmariadb-dev-compat \ - && rm mariadb_repo_setup \ - # Cleaning up apt cache space - && rm -rf /var/lib/apt/lists/* + awscli + +# Allow running datadog-ci in CI with npx +RUN apt-get install -y --no-install-recommends nodejs npm \ + && npm install -g @datadog/datadog-ci + +# MariaDB is a dependency for tests +RUN curl https://mariadb.org/mariadb_release_signing_key.pgp | gpg --dearmor > /etc/apt/trusted.gpg.d/mariadb.gpg \ + && echo "deb [arch=amd64,arm64] https://mirror.mariadb.org/repo/11.rolling/debian/ buster main" > /etc/apt/sources.list.d/mariadb.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends libmariadb-dev libmariadb-dev-compat + +# Install azure-functions-core-tools-4, only supported on amd64 architecture for Linux +# https://github.com/Azure/azure-functions-core-tools/issues/3112 +RUN if [ "$TARGETARCH" = "amd64" ]; \ + then \ + curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ + && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \ + && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-buster-prod buster main" > /etc/apt/sources.list.d/dotnetdev.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends azure-functions-core-tools-4=4.0.6280-1; \ + fi + +# Google Chrome is needed for selenium contrib tests but is currently only available on amd64 +RUN if [ "$TARGETARCH" = "amd64" ]; \ + then \ + curl https://dl.google.com/linux/linux_signing_key.pub |gpg --dearmor > /etc/apt/trusted.gpg.d/google.gpg \ + && echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' > /etc/apt/sources.list.d/google-chrome.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends google-chrome-stable ; \ + fi + +# Cleaning up apt cache space +RUN rm -rf /var/lib/apt/lists/* # Install Rust toolchain RUN curl https://sh.rustup.rs -sSf | \ From 79be26d5eab35fa7368a2421d40662a38e50b619 Mon Sep 17 00:00:00 2001 From: Rachel Yang Date: Wed, 4 Dec 2024 14:57:11 -0500 Subject: [PATCH 253/372] feat(baggage): otel drop in support for baggage (#11494) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/opentelemetry/context.py | 24 +++++++++++ tests/opentelemetry/test_context.py | 50 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/ddtrace/internal/opentelemetry/context.py b/ddtrace/internal/opentelemetry/context.py index 0817f5d1da2..d0f7294eed5 100644 --- a/ddtrace/internal/opentelemetry/context.py +++ b/ddtrace/internal/opentelemetry/context.py @@ -21,6 +21,7 @@ def attach(self, otel_context): Datadog representation in the Global DDtrace Trace Context Provider. """ # Inline opentelemetry imports to avoid circular imports. + from opentelemetry.baggage import get_all from opentelemetry.trace import Span as OtelSpan from opentelemetry.trace import get_current_span @@ -30,18 +31,29 @@ def attach(self, otel_context): if otel_span: if isinstance(otel_span, Span): self._ddcontext_provider.activate(otel_span._ddspan) + ddcontext = otel_span._ddspan.context elif isinstance(otel_span, OtelSpan): trace_id, span_id, _, tf, ts, _ = otel_span.get_span_context() trace_state = ts.to_header() if ts else None ddcontext = _TraceContext._get_context(trace_id, span_id, tf, trace_state) self._ddcontext_provider.activate(ddcontext) else: + ddcontext = None log.error( "Programming ERROR: ddtrace does not support activating spans with the type: %s. Please open a " "github issue at: https://github.com/Datadog/dd-trace-py and set DD_TRACE_OTEL_ENABLED=True.", type(otel_span), ) + # get current open telemetry baggage and store it on the datadog context object + # fix: we need to support setting baggage when there is no active span + otel_baggage = get_all(otel_context) + if ddcontext: + ddcontext.remove_all_baggage_items() + if otel_baggage: + for key, value in otel_baggage.items(): + ddcontext._baggage[key] = value # potentially convert to json + # A return value with the type `object` is required by the otel api to remove/deactivate spans. # Since manually deactivating spans is not supported by ddtrace this object will never be used. return object() @@ -52,6 +64,7 @@ def get_current(self): in a format that can be parsed by the OpenTelemetry API. """ # Inline opentelemetry imports to avoid circular imports. + from opentelemetry.baggage import set_baggage from opentelemetry.context.context import Context as OtelContext from opentelemetry.trace import NonRecordingSpan as OtelNonRecordingSpan from opentelemetry.trace import SpanContext as OtelSpanContext @@ -72,6 +85,17 @@ def get_current(self): span_context = OtelSpanContext(ddactive.trace_id or 0, ddactive.span_id or 0, True, tf, ts) span = OtelNonRecordingSpan(span_context) context = set_span_in_context(span, context) + + if isinstance(ddactive, DDSpan): + dd_baggage = ddactive.context._baggage + elif isinstance(ddactive, DDContext): + dd_baggage = ddactive._baggage + else: + dd_baggage = {} + + for key, value in dd_baggage.items(): + context = set_baggage(key, value, context) + return context def detach(self, token): diff --git a/tests/opentelemetry/test_context.py b/tests/opentelemetry/test_context.py index 37c1e6cf74f..dffa3f715cd 100644 --- a/tests/opentelemetry/test_context.py +++ b/tests/opentelemetry/test_context.py @@ -3,6 +3,10 @@ import time import opentelemetry +from opentelemetry.baggage import get_baggage +from opentelemetry.baggage import remove_baggage +from opentelemetry.baggage import set_baggage +from opentelemetry.context import attach import pytest import ddtrace @@ -129,3 +133,49 @@ async def coro(i): await coro(2) await coro(3) await coro(4) + + +def test_otel_baggage_propagation_to_ddtrace(oteltracer): + with oteltracer.start_as_current_span("otel-baggage-inject") as span: # noqa: F841 + baggage_context = set_baggage("key1", "value1") + baggage_context = set_baggage("key2", "value2", baggage_context) + attach(baggage_context) + with ddtrace.tracer.trace("ddtrace-baggage-inject") as ddspan: + assert ddspan.context.get_baggage_item("key1") == "value1" + assert ddspan.context.get_baggage_item("key2") == "value2" + + +def test_ddtrace_baggage_propagation_to_otel(oteltracer): + with ddtrace.tracer.trace("ddtrace-baggage") as ddspan: + ddspan.context.set_baggage_item("key1", "value1") + ddspan.context.set_baggage_item("key2", "value2") + assert get_baggage("key1") == "value1" + assert get_baggage("key2") == "value2" + + +def test_conflicting_otel_and_ddtrace_baggage(oteltracer): + with ddtrace.tracer.trace("ddtrace-baggage") as ddspan: + ddspan.context.set_baggage_item("key1", "dd1") + attach(set_baggage("key1", "otel1")) + attach(set_baggage("key2", "otel2")) + ddspan.context.set_baggage_item("key2", "dd2") + assert get_baggage("key1") == "otel1" + assert get_baggage("key2") == "dd2" + + +def test_otel_baggage_removal_propagation_to_ddtrace(oteltracer): + with oteltracer.start_as_current_span("otel-baggage-inject") as span: # noqa: F841 + baggage_context = set_baggage("key1", "value1") + baggage_context = set_baggage("key2", "value2", baggage_context) + attach(baggage_context) + baggage_context = set_baggage("key3", "value3") + baggage_context = set_baggage("key4", "value4", baggage_context) + baggage_context = remove_baggage("key1", baggage_context) + baggage_context = remove_baggage("key2", baggage_context) + attach(baggage_context) + with ddtrace.tracer.trace("ddtrace-baggage-inject") as ddspan: + # newest baggage set in otel should take precedence + assert ddspan.context.get_baggage_item("key3") == "value3" + assert ddspan.context.get_baggage_item("key4") == "value4" + assert ddspan.context.get_baggage_item("key1") is None + assert ddspan.context.get_baggage_item("key2") is None From 13f4202d3e2fe94c601fd72ac4522d0fa9bb0439 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:14:16 +0000 Subject: [PATCH 254/372] ci: update image and standardize to using both hash and sha256 (#11617) This updates the image to the one built in 47c7b5287da25643e46652e6d222a40a52f2382a , with: - fixed MariaDB repo - new `azure-functions-core-tools-4` for serverless - new `google-chrome-stable` installation to support the Selenium contrib Additionally, it switches all references to image to include the `label@digest` syntax in order to make easier to: - know which commit an image comes from - keep the digest in order to disambiguate any potential case where the same label has different digests ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 2 +- .github/workflows/requirements-locks.yml | 2 +- .gitlab/testrunner.yml | 2 +- .gitlab/tests.yml | 2 +- docker-compose.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index d8347ed8643..b26856fc533 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -4,7 +4,7 @@ default_resource_class: &default_resource_class medium ubuntu_base_image: &ubuntu_base_img ubuntu-2004:2023.04.2 cimg_base_image: &cimg_base_image cimg/base:2022.08 python310_image: &python310_image cimg/python:3.10.12 -ddtrace_dev_image: &ddtrace_dev_image ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 +ddtrace_dev_image: &ddtrace_dev_image ghcr.io/datadog/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 redis_image: &redis_image redis:4.0-alpine@sha256:3e99741f293147ff406657dda7644c2b88564b80a498cd00da8f905743449c9f memcached_image: &memcached_image memcached:1.5-alpine@sha256:48cb7207e3d34871893fa1628f3a4984375153e9942facf82e25935b0a633c8a cassandra_image: &cassandra_image cassandra:3.11.7@sha256:495e5752526f7e75d3ad85b6a6bbf3b79714321b17a44255a216c341e3baae11 diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index f64c17bc413..071d8a879a1 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -11,7 +11,7 @@ jobs: validate: name: Check requirements lockfiles runs-on: ubuntu-latest - container: ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 + container: ghcr.io/datadog/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 steps: - uses: actions/checkout@v4 with: diff --git a/.gitlab/testrunner.yml b/.gitlab/testrunner.yml index bdee810249a..f1fd4806506 100644 --- a/.gitlab/testrunner.yml +++ b/.gitlab/testrunner.yml @@ -1,5 +1,5 @@ .testrunner: - image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:7a2e802af76051f82d698919d2837eff18dbb48e + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 # DEV: we have a larger pool of amd64 runners, prefer that over arm64 tags: [ "arch:amd64" ] timeout: 20m diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 01c36591e10..b4801698020 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -9,7 +9,7 @@ variables: # CI_DEBUG_SERVICES: "true" .testrunner: - image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:7a2e802af76051f82d698919d2837eff18dbb48e + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 # DEV: we have a larger pool of amd64 runners, prefer that over arm64 tags: [ "arch:amd64" ] timeout: 20m diff --git a/docker-compose.yml b/docker-compose.yml index af3e559e7f4..bed5a6ce8ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,7 +152,7 @@ services: - "127.0.0.1:5433:5433" testrunner: - image: ghcr.io/datadog/dd-trace-py/testrunner@sha256:8ca43d46ff34e078bd7bc0662e74e6be38547a98140a5cd4203805f6b214b583 + image: ghcr.io/datadog/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 command: bash environment: - TOX_SKIP_DIST=True From f2ac8b87319f0e0d423521cb9f40c9d0e546f561 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:57:54 +0100 Subject: [PATCH 255/372] ci(test): make serverless tests mandatory again (#11625) serverless benchmark tests were disabled. This PR makes them mandatory again. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/benchmarks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 6f0de408e83..9d56afcdf09 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -84,7 +84,6 @@ benchmark-serverless: tags: ["arch:amd64"] when: on_success needs: [ "benchmark-serverless-trigger" ] - allow_failure: true script: - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ddbuild.io/DataDog/serverless-tools.git ./serverless-tools && cd ./serverless-tools - ./ci/check_trigger_status.sh From 146567f9f1afabd80a8eabe5349dd52273d31e3f Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 5 Dec 2024 09:31:56 -0500 Subject: [PATCH 256/372] chore(profiling): upgrade googletest (#11620) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt | 2 +- ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index e7fe2ceb3ed..6a1e7f406f6 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.11.0) + GIT_TAG v1.15.2) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt index dd8e149f54c..926f9b28af7 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.11.0) + GIT_TAG v1.15.2) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) From 7f9c6194b1aaa0e202ee00fd67869962d4d1c128 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 5 Dec 2024 09:42:33 -0500 Subject: [PATCH 257/372] chore(profiling): use clang when --safety is set (#11619) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/datadog/profiling/build_standalone.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/internal/datadog/profiling/build_standalone.sh b/ddtrace/internal/datadog/profiling/build_standalone.sh index 054ff6e9d64..286f6d179a2 100755 --- a/ddtrace/internal/datadog/profiling/build_standalone.sh +++ b/ddtrace/internal/datadog/profiling/build_standalone.sh @@ -245,7 +245,7 @@ add_compiler_args() { ;; -s|--safety) cmake_args+=(${compiler_args["safety"]}) - set_gcc + set_clang ;; -t|--thread) cmake_args+=(${compiler_args["thread"]}) From 003c1f7a81579fc1fabd73ee30d56d5240f5ff2a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 5 Dec 2024 09:42:58 -0500 Subject: [PATCH 258/372] chore(profiling): update agent url and port for native tests (#11621) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/test/test_api.cpp | 10 +++++----- .../datadog/profiling/dd_wrapper/test/test_forking.cpp | 4 ++-- .../profiling/dd_wrapper/test/test_initialization.cpp | 4 ++-- .../profiling/dd_wrapper/test/test_threading.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp index 7ae7c009317..3b9ba35af75 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_api.cpp @@ -10,7 +10,7 @@ void single_sample_noframe() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); // Collect and flush one sample auto h = ddup_start_sample(); @@ -33,7 +33,7 @@ TEST(UploadDeathTest, SingleSample) void single_oneframe_sample() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); // Collect and flush one sample with one frame auto h = ddup_start_sample(); @@ -57,7 +57,7 @@ TEST(UploadDeathTest, SingleSampleOneFrame) void single_manyframes_sample() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 512); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 512); // Collect and flush one sample with one frame auto h = ddup_start_sample(); @@ -89,7 +89,7 @@ TEST(UploadDeathTest, SingleSampleManyFrames) void single_toomanyframes_sample() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 512); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 512); // Collect and flush one sample with one frame auto h = ddup_start_sample(); @@ -121,7 +121,7 @@ TEST(UploadDeathTest, SingleSampleTooManyFrames) void lotsa_frames_lotsa_samples() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 512); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 512); // 60 seconds @ 100 hertz for (int i = 0; i < 60 * 100; i++) { diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp index e02849248e6..1c5ecb322c2 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp @@ -38,7 +38,7 @@ is_exit_normal(int status) void sample_in_threads_and_fork(unsigned int num_threads, unsigned int sleep_time_ns) { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); std::atomic done(false); std::vector threads; std::vector ids; @@ -75,7 +75,7 @@ sample_in_threads_and_fork(unsigned int num_threads, unsigned int sleep_time_ns) void fork_stress_test(unsigned int num_threads, unsigned int sleep_time_ns, unsigned int num_children) { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); std::atomic done(false); std::vector threads; std::vector ids; diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp index fd1404f6960..e4729c9eeb6 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_initialization.cpp @@ -9,7 +9,7 @@ void simple_init() { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); std::exit(0); } @@ -51,7 +51,7 @@ short_lifetime_init() std::string service("my_test_service"); std::string env("my_test_env"); std::string version("0.0.1"); - std::string url("https://localhost:8126"); + std::string url("https://127.0.0.1:9126"); std::string runtime("cpython"); std::string runtime_version("3.10.6"); std::string profiler_version("3.100"); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp index 63658cabf07..1ea48c8ccce 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_threading.cpp @@ -24,7 +24,7 @@ generic_launch_sleep_upload(int n, unsigned int sleep_time_ns) void emulate_profiler(unsigned int num_threads, unsigned int sample_ns) { - configure("my_test_service", "my_test_env", "0.0.1", "https://localhost:8126", "cpython", "3.10.6", "3.100", 256); + configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); generic_launch_sleep_upload(num_threads, sample_ns); // Assumed to execute within a thread From aac073b931644f9d3dd089f941a4da826c4fb7ad Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:08:56 -0800 Subject: [PATCH 259/372] chore: use latest patched riot version in CI (#11632) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 2 +- .circleci/config.yml | 2 +- .github/workflows/generate-package-versions.yml | 6 +++--- .github/workflows/requirements-locks.yml | 2 +- .gitlab-ci.yml | 2 +- .gitlab/tests.yml | 4 ++-- docs/contributing-testing.rst | 2 +- hatch.toml | 2 +- scripts/ddtest | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index b26856fc533..0dee5155002 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -80,7 +80,7 @@ commands: description: "Install riot" steps: # Make sure we install and run riot on Python 3 - - run: pip3 install riot==0.20.0 + - run: pip3 install riot==0.20.1 setup_rust: description: "Install rust toolchain" diff --git a/.circleci/config.yml b/.circleci/config.yml index fcf74292ee3..a9334a11859 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: name: Generate config command: | export GIT_COMMIT_DESC=$(git log -n 1 $CIRCLE_SHA1) - pip3 install riot==0.20.0 + pip3 install riot==0.20.1 riot -P -v run --pass-env -s circleci-gen-config -- -v - continuation/continue: configuration_path: .circleci/config.gen.yml diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index e674c039b4c..70a1a83adbf 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -77,7 +77,7 @@ jobs: python -m pip install --upgrade pip pip install packaging pip install requests - pip install riot==0.20.0 + pip install riot==0.20.1 - name: Run regenerate-riot-latest run: scripts/regenerate-riot-latest.sh @@ -104,7 +104,7 @@ jobs: This performs the following updates: 1) Some ${{ env.VENV_NAME }} lockfiles use ${{ env.VENV_NAME }} `latest`. This will update ${{ env.VENV_NAME }} and dependencies. 2) Some ${{ env.VENV_NAME }} lockfiles use a pinned (non-latest) version of ${{ env.VENV_NAME }}, but require the `latest` version of another package. This will update all such packages. - + ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change @@ -117,7 +117,7 @@ jobs: - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - - [ ] Reviewer has checked that all the criteria below are met + - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index 071d8a879a1..a504ee43a75 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -25,7 +25,7 @@ jobs: run: pyenv global 3.10 3.7 3.8 3.9 3.11 3.12 - name: Install Dependencies - run: pip install --upgrade pip && pip install riot==0.20.0 + run: pip install --upgrade pip && pip install riot==0.20.1 - name: Generate riot locks run: scripts/compile-and-prune-test-requirements diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b29529e6106..b05126541d2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,7 +27,7 @@ tests-gen: stage: tests-gen extends: .testrunner script: - - pip install riot==0.20.0 + - pip install riot==0.20.1 - riot -v run --pass-env -s gitlab-gen-config -v needs: [] artifacts: diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index b4801698020..83a5d4231b8 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -66,7 +66,7 @@ build_base_venvs: PIP_VERBOSE: 1 DD_PROFILING_NATIVE_TESTS: 1 script: - - pip install riot==0.20.0 + - pip install riot==0.20.1 - riot -P -v generate --python=$PYTHON_VERSION artifacts: name: venv_$PYTHON_VERSION @@ -86,7 +86,7 @@ build_base_venvs: # DEV: This is the max retries that GitLab currently allows for retry: 2 script: - - pip install riot==0.20.0 + - pip install riot==0.20.1 - unset DD_SERVICE - unset DD_ENV - unset DD_TAGS diff --git a/docs/contributing-testing.rst b/docs/contributing-testing.rst index c096e0407ba..245c44713eb 100644 --- a/docs/contributing-testing.rst +++ b/docs/contributing-testing.rst @@ -35,7 +35,7 @@ In addition, you will need `riot `_ an .. code-block:: bash - $ pip install riot==0.19.1 + $ pip install riot==0.20.1 Refer `hatch install `_ for installation instructions diff --git a/hatch.toml b/hatch.toml index 056ad593722..11238889c22 100644 --- a/hatch.toml +++ b/hatch.toml @@ -17,7 +17,7 @@ dependencies = [ "ddapm-test-agent>=1.2.0", "packaging==23.1", "pygments==2.16.1", - "riot==0.20.0", + "riot==0.20.1", "ruff==0.1.3", "clang-format==18.1.5", "cmake-format==0.6.13", diff --git a/scripts/ddtest b/scripts/ddtest index d4bc6e90a0c..b0561262a71 100755 --- a/scripts/ddtest +++ b/scripts/ddtest @@ -19,7 +19,7 @@ fi for i in {1..3}; do $compose_cmd pull -q testrunner && break || sleep 3; done # TODO(DEV): Install riot in the docker image -FULL_CMD="pip install -q --disable-pip-version-check riot==0.20.0 && $CMD" +FULL_CMD="pip install -q --disable-pip-version-check riot==0.20.1 && $CMD" # install and upgrade riot in case testrunner image has not been updated # DEV: Use `--no-TTY` and `--quiet-pull` when running in CircleCI From 4ca600932d5a6aaa5bacc240238426b42bbe0813 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:00:44 +0000 Subject: [PATCH 260/372] chore(internal): avoid freezegun in ddtrace (#11406) `freezegun` (and `pytest-freezegun`) may cause wrong durations to be reported when used, so we add an integration that automatically enforces that `ddtrace` be an ignored module by: - calling `freezegun.configure()` with `extend_ignore_list` to add `ddtrace` - wrapping `freezegun.configure()` so that any attempt to reset the `default_ignore_list` includes `ddtrace` Additionally, since `pytest` does not patch all integrations by default, and because we don't want to force users to use `--ddtrace-patch-all` for `freezegun` to be patched, we update our `pytest` plugin(s) to patch `freezegun` during the `pytest_load_initial_conftests` hook (which should be early enough). This is not considered a fix as there are no active issues reported and this (so far) primarily affects telemetry recorded by the CI Visibility service. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/CODEOWNERS | 4 + ddtrace/_monkey.py | 1 + ddtrace/contrib/freezegun/__init__.py | 28 +++++ ddtrace/contrib/internal/freezegun/patch.py | 68 +++++++++++++ ddtrace/contrib/pytest/_plugin_v1.py | 3 + ddtrace/contrib/pytest/_plugin_v2.py | 3 + hatch.toml | 21 ++++ tests/contrib/freezegun/test_freezegun.py | 107 ++++++++++++++++++++ tests/contrib/suitespec.yml | 13 +++ 9 files changed, 248 insertions(+) create mode 100644 ddtrace/contrib/freezegun/__init__.py create mode 100644 ddtrace/contrib/internal/freezegun/patch.py create mode 100644 tests/contrib/freezegun/test_freezegun.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4d12f8aeb93..b673cc34a94 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,6 +77,10 @@ tests/coverage @DataDog/apm-core-python @ tests/tracer/test_ci.py @DataDog/ci-app-libraries ddtrace/ext/git.py @DataDog/ci-app-libraries @DataDog/apm-core-python scripts/ci_visibility/* @DataDog/ci-app-libraries +# Test Visibility owns the freezegun integration because it's the team most affected by it +ddtrace/contrib/freezegun @DataDog/ci-app-libraries +ddtrace/contrib/internal/freezegun @DataDog/ci-app-libraries +tests/contrib/freezegun @DataDog/ci-app-libraries # Debugger ddtrace/debugging/ @DataDog/debugger-python diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index d203ca19e24..61c1ac3770b 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -41,6 +41,7 @@ "elasticsearch": True, "algoliasearch": True, "futures": True, + "freezegun": True, "google_generativeai": True, "gevent": True, "graphql": True, diff --git a/ddtrace/contrib/freezegun/__init__.py b/ddtrace/contrib/freezegun/__init__.py new file mode 100644 index 00000000000..7e2df3c557c --- /dev/null +++ b/ddtrace/contrib/freezegun/__init__.py @@ -0,0 +1,28 @@ +""" +The freezegun integration reconfigures freezegun's default ignore list to ignore ddtrace. + +Enabling +~~~~~~~~ +The freezegun integration is enabled by default. Use :func:`patch()` to enable the integration:: + from ddtrace import patch + patch(freezegun=True) + + +Configuration +~~~~~~~~~~~~~ +The freezegun integration is not configurable, but may be disabled using DD_PATCH_MODULES=freezegun:false . +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["freezegun"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + # Expose public methods + from ..internal.freezegun.patch import get_version + from ..internal.freezegun.patch import patch + from ..internal.freezegun.patch import unpatch + + __all__ = ["get_version", "patch", "unpatch"] diff --git a/ddtrace/contrib/internal/freezegun/patch.py b/ddtrace/contrib/internal/freezegun/patch.py new file mode 100644 index 00000000000..f10a1292860 --- /dev/null +++ b/ddtrace/contrib/internal/freezegun/patch.py @@ -0,0 +1,68 @@ +from ddtrace.internal.logger import get_logger +from ddtrace.internal.wrapping.context import WrappingContext + + +log = get_logger(__name__) + +DDTRACE_MODULE_NAME = "ddtrace" + + +class FreezegunConfigWrappingContext(WrappingContext): + """Wraps the call to freezegun.configure to ensure that ddtrace remains patched if default_ignore_list is passed + in as an argument + """ + + # __exit__ comes from the parent class + # no-dd-sa:python-best-practices/ctx-manager-enter-exit-defined + def __enter__(self) -> "FreezegunConfigWrappingContext": + super().__enter__() + try: + default_ignore_list = self.get_local("default_ignore_list") + except KeyError: + log.debug("Could not get default_ignore_list on call to configure()") + return + + if default_ignore_list is not None and DDTRACE_MODULE_NAME not in default_ignore_list: + default_ignore_list.append(DDTRACE_MODULE_NAME) + + return self + + +def get_version() -> str: + import freezegun + + try: + return freezegun.__version__ + except AttributeError: + log.debug("Could not get freezegun version") + return "" + + +def patch() -> None: + import freezegun + + if getattr(freezegun, "_datadog_patch", False): + return + + FreezegunConfigWrappingContext(freezegun.configure).wrap() + + freezegun.configure(extend_ignore_list=[DDTRACE_MODULE_NAME]) + + freezegun._datadog_patch = True + + +def unpatch() -> None: + import freezegun + + if not getattr(freezegun, "_datadog_patch", False): + return + + if FreezegunConfigWrappingContext.is_wrapped(freezegun.configure): + FreezegunConfigWrappingContext.extract(freezegun.configure).unwrap() + + # Note: we do not want to restore to the original ignore list, as it may have been modified by the user, but we do + # want to remove the ddtrace module from the ignore list + new_ignore_list = [m for m in freezegun.config.settings.default_ignore_list if m != DDTRACE_MODULE_NAME] + freezegun.configure(default_ignore_list=new_ignore_list) + + freezegun._datadog_patch = False diff --git a/ddtrace/contrib/pytest/_plugin_v1.py b/ddtrace/contrib/pytest/_plugin_v1.py index acdc25a594e..e7b7b2caac9 100644 --- a/ddtrace/contrib/pytest/_plugin_v1.py +++ b/ddtrace/contrib/pytest/_plugin_v1.py @@ -423,6 +423,9 @@ def pytest_load_initial_conftests(early_config, parser, args): log = get_logger(__name__) + # Freezegun is proactively patched to avoid it interfering with internal timing + ddtrace.patch(freezegun=True) + COVER_SESSION = asbool(os.environ.get("_DD_COVER_SESSION", "false")) if USE_DD_COVERAGE: diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index 8fff94872c7..b9cf7daf564 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -6,6 +6,7 @@ from ddtrace import DDTraceDeprecationWarning from ddtrace import config as dd_config +from ddtrace import patch from ddtrace.contrib.coverage import patch as patch_coverage from ddtrace.contrib.internal.coverage.constants import PCT_COVERED_KEY from ddtrace.contrib.internal.coverage.data import _coverage_data @@ -163,6 +164,8 @@ def pytest_load_initial_conftests(early_config, parser, args): try: take_over_logger_stream_handler() log.warning("This version of the ddtrace pytest plugin is currently in beta.") + # Freezegun is proactively patched to avoid it interfering with internal timing + patch(freezegun=True) dd_config.test_visibility.itr_skipping_level = ITR_SKIPPING_LEVEL.SUITE enable_test_visibility(config=dd_config.pytest) if InternalTestSession.should_collect_coverage(): diff --git a/hatch.toml b/hatch.toml index 11238889c22..668a7ef5497 100644 --- a/hatch.toml +++ b/hatch.toml @@ -450,3 +450,24 @@ dependencies = [ view = [ "python scripts/ci_visibility/view_snapshot.py {args:}", ] + +[envs.freezegun] +template = "freezegun" +dependencies = [ + "freezegun{matrix:freezegun}", + "pytest", + "pytest-cov", + "hypothesis", +] + +[envs.freezegun.env-vars] +DD_PYTEST_USE_NEW_PLUGIN_BETA = "true" + +[envs.freezegun.scripts] +test = [ + "pytest tests/contrib/freezegun {args:}", +] + +[[envs.freezegun.matrix]] +python = ["3.7", "3.10", "3.12"] +freezegun = ["~=1.3.0", "~=1.5.0"] diff --git a/tests/contrib/freezegun/test_freezegun.py b/tests/contrib/freezegun/test_freezegun.py new file mode 100644 index 00000000000..a1e624ed0eb --- /dev/null +++ b/tests/contrib/freezegun/test_freezegun.py @@ -0,0 +1,107 @@ +import datetime +import os +import time + +import pytest + +from ddtrace import tracer as dd_tracer +from ddtrace.internal.utils.time import StopWatch +from tests.contrib.pytest.test_pytest import PytestTestCaseBase + + +class TestFreezegunTestCase: + @pytest.fixture(autouse=True) + def _patch_freezegun(self): + from ddtrace.contrib.freezegun import patch + from ddtrace.contrib.freezegun import unpatch + + patch() + yield + unpatch() + + def test_freezegun_unpatch(self): + import freezegun + + from ddtrace.contrib.freezegun import unpatch + + unpatch() + + with freezegun.freeze_time("2020-01-01"): + with dd_tracer.trace("freezegun.test") as span: + time.sleep(1) + + assert span.duration == 0 + + def test_freezegun_does_not_freeze_tracing(self): + import freezegun + + with freezegun.freeze_time("2020-01-01"): + with dd_tracer.trace("freezegun.test") as span: + time.sleep(1) + + assert span.duration >= 1 + + def test_freezegun_fast_forward_does_not_affect_tracing(self): + import freezegun + + with freezegun.freeze_time("2020-01-01") as frozen_time: + with dd_tracer.trace("freezegun.test") as span: + time.sleep(1) + frozen_time.tick(delta=datetime.timedelta(days=10)) + assert 1 <= span.duration <= 5 + + def test_freezegun_does_not_freeze_stopwatch(self): + import freezegun + + with freezegun.freeze_time("2020-01-01"): + with StopWatch() as sw: + time.sleep(1) + assert sw.elapsed() >= 1 + + def test_freezegun_configure_default_ignore_list_continues_to_ignore_ddtrace(self): + import freezegun + + freezegun.configure(default_ignore_list=[]) + + with freezegun.freeze_time("2020-01-01"): + with dd_tracer.trace("freezegun.test") as span: + time.sleep(1) + + assert span.duration >= 1 + + +class PytestFreezegunTestCase(PytestTestCaseBase): + def test_freezegun_pytest_plugin(self): + """Tests that pytest's patching of freezegun in the v1 plugin version works""" + import sys + + from ddtrace.contrib.freezegun import unpatch + + unpatch() + if "freezegun" in sys.modules: + del sys.modules["freezegun"] + + py_file = self.testdir.makepyfile( + """ + import datetime + import time + + import freezegun + + from ddtrace import tracer as dd_tracer + + def test_pytest_patched_freezegun(): + with freezegun.freeze_time("2020-01-01"): + with dd_tracer.trace("freezegun.test") as span: + time.sleep(1) + assert span.duration >= 1 + + """ + ) + file_name = os.path.basename(py_file.strpath) + self.inline_run("--ddtrace", "-s", file_name) + spans = self.pop_spans() + + assert len(spans) == 4 + for span in spans: + assert span.get_tag("test.status") == "pass" diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index fd8ac2c4e08..2f14127ddf0 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -97,6 +97,9 @@ components: - ddtrace/contrib/flask_cache/* - ddtrace/contrib/internal/flask_cache/* - ddtrace/contrib/flask_login/* + freezegun: + - ddtrace/contrib/freezegun/* + - ddtrace/contrib/internal/freezegun/* futures: - ddtrace/contrib/futures/* - ddtrace/contrib/internal/futures/* @@ -609,6 +612,16 @@ suites: - memcached - redis snapshot: true + freezegun: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@freezegun' + - tests/contrib/freezegun/* + runner: hatch + snapshot: true gevent: paths: - '@bootstrap' From 474dfb17d404675b40fafe19cecb4a74e59adfb3 Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Mon, 9 Dec 2024 05:59:40 -0800 Subject: [PATCH 261/372] fix(profiling): get rid of leaky string table (#11612) Profiler samples typically contain several strings, for sample labels (task name, endpoint/resource, etc) and frame info. Those strings tend to be repeated. We build samples across several API calls, from Python, Cython, C Python extensions, and C++ code, making it hard to reason about the lifetimes of the strings we get. So, it's easier to just copy the strings we need while building a sample. And it's preferably to only store one copy. So, our libdatadog wrapper uses a string table, which addresses both of these concerns. However, nothing is ever removed from the table. And some things like task names can have new values each time, meaning we have unbounded memory growth. I previously tried to fix this by periodically clearing the string table. However, this comes with its own new problems. We can construct samples in parallel with profile uploading. So if we try to clear the table when we upload the profile, or really any other time, we need to make sure in-progress samples keep their strings around until the sample is flushed to libdatadog. And we need to handle forking so we don't leak a whole string table on fork. It turns out we don't really need that string table at all. Libdatadog already interns strings once the sample makes it into the libdatadog profile. That solves the duplicate string problem. We still want to copy strings while building the sample, but we can just do that on a per-sample basis, and the strings only need to be alive while we build a sample. We can use a small amount of scratch space for each sample to copy strings. We will likely only have a few samples in flight at any time, and we already pool the samples. So we can reuse this scratch memory effectively. We'll only use as memory in proportion to the number of samples being built concurrently. So, this commit implements a simple string arena for each sample object to use. We may want to tweak the default buffer size and how much memory we keep around when resetting the arena. We could even deduplicate, since we might see some duplicates per-sample from frame info. PROF-10824 --- .../profiling/dd_wrapper/include/profile.hpp | 14 ------- .../profiling/dd_wrapper/include/sample.hpp | 41 +++++++++++++++++++ .../profiling/dd_wrapper/src/profile.cpp | 15 ------- .../profiling/dd_wrapper/src/sample.cpp | 41 +++++++++++++++++-- ...ounded-string-buffer-d18056263f3e4bdc.yaml | 6 +++ 5 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/profiling-fix-unbounded-string-buffer-d18056263f3e4bdc.yaml diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/profile.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/profile.hpp index d79c99f75f7..59204384b6a 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/profile.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/profile.hpp @@ -4,12 +4,10 @@ #include "types.hpp" #include -#include #include #include #include #include -#include #include extern "C" @@ -19,10 +17,6 @@ extern "C" namespace Datadog { -// Unordered containers don't get heterogeneous lookup until gcc-10, so for now use this -// strategy to dedup + store strings. -using StringTable = std::unordered_set; - // Serves to collect individual samples, as well as lengthen the scope of string data class Profile { @@ -33,11 +27,6 @@ class Profile std::atomic first_time{ true }; std::mutex profile_mtx{}; - // Storage for strings - std::deque string_storage{}; - StringTable strings{}; - std::mutex string_table_mtx{}; - // Configuration SampleType type_mask{ 0 }; unsigned int max_nframes{ g_default_max_nframes }; @@ -69,9 +58,6 @@ class Profile ddog_prof_Profile& profile_borrow(); void profile_release(); - // String table manipulation - std::string_view insert_or_get(std::string_view str); - // constref getters const ValueIndex& val(); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp index 56d356fa9b6..8ddf412bf89 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp @@ -15,6 +15,44 @@ extern "C" namespace Datadog { +namespace internal { + +// StringArena holds copies of strings we need while building samples. +// StringArena is intended to amortize allocations, so that in the common +// case we can just do a memcpy for each string we want to copy rather than +// a new allocation for each string. +// +// We need to make copies right now because we don't have strong guarantees +// that the strings we get (from Python, Cython, C++, etc) are alive the +// whole time we build samples. +struct StringArena +{ + // Default size, in bytes, of each Chunk. The value is a power of 2 (nice to + // allocate) that is bigger than any actual sample string size seen over a + // random selection of a few hundred Python profiles at Datadog. So ideally + // we only need one chunk, which we can reuse between samples + static constexpr size_t DEFAULT_SIZE = 16 * 1024; + // Strings are backed by fixed-size Chunks. The Chunks can't grow, or + // they'll move and invalidate pointers into the arena. At the same time, + // they must be dynamically sized at creation because we get arbitrary + // user-provided strings. + using Chunk = std::vector; + // We keep the Chunks for this arena in a vector so we can track them, and + // free them when the StringArena is deallocated. + std::vector chunks; + + StringArena(); + // Clear the backing data of the arena, except for a smaller initial segment. + // Views returned by insert are invalid after this call. + void reset(); + // Copies the contents of s into the arena and returns a view of the copy in + // the arena. The returned view is valid until the next call to reset, or + // until the arena is destroyed. + std::string_view insert(std::string_view s); +}; + +} // namespace internal + class SampleManager; // friend class Sample @@ -45,6 +83,9 @@ class Sample // Additional metadata int64_t endtime_ns = 0; // end of the event + // Backing memory for string copies + internal::StringArena string_storage{}; + public: // Helpers bool push_label(ExportLabelKey key, std::string_view val); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp index 6f986f4896b..f9f7a3e9585 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp @@ -162,21 +162,6 @@ Datadog::Profile::one_time_init(SampleType type, unsigned int _max_nframes) first_time.store(false); } -std::string_view -Datadog::Profile::insert_or_get(std::string_view str) -{ - const std::lock_guard lock(string_table_mtx); // Serialize access - - auto str_it = strings.find(str); - if (str_it != strings.end()) { - return *str_it; - } - - string_storage.emplace_back(str); - strings.insert(string_storage.back()); - return string_storage.back(); -} - const Datadog::ValueIndex& Datadog::Profile::val() { diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp index f8719a59d1e..bc0a316bcc3 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp @@ -4,8 +4,42 @@ #include #include +#include #include +Datadog::internal::StringArena::StringArena() +{ + chunks.emplace_back(); + chunks.back().reserve(Datadog::internal::StringArena::DEFAULT_SIZE); +} + +void +Datadog::internal::StringArena::reset() +{ + // Free chunks. Keep the first one around so it's easy to reuse this without + // needing new allocations every time. We can completely drop it to get rid + // of everything + // TODO - we could consider keeping more around if it's not too costly. + // The goal is to not retain more than we need _on average_. If we have + // mostly small samples and then a rare huge one, we can end up with + // all samples in our pool using as much memory as the largets ones we've seen + chunks.front().clear(); + chunks.erase(++chunks.begin(), chunks.end()); +} + +std::string_view +Datadog::internal::StringArena::insert(std::string_view s) +{ + auto chunk = &chunks.back(); + if ((chunk->capacity() - chunk->size()) < s.size()) { + chunk = &chunks.emplace_back(); + chunk->reserve(std::max(s.size(), Datadog::internal::StringArena::DEFAULT_SIZE)); + } + int base = chunk->size(); + chunk->insert(chunk->end(), s.begin(), s.end()); + return std::string_view(chunk->data() + base, s.size()); +} + Datadog::Sample::Sample(SampleType _type_mask, unsigned int _max_nframes) : max_nframes{ _max_nframes } , type_mask{ _type_mask } @@ -28,8 +62,8 @@ void Datadog::Sample::push_frame_impl(std::string_view name, std::string_view filename, uint64_t address, int64_t line) { static const ddog_prof_Mapping null_mapping = { 0, 0, 0, to_slice(""), to_slice("") }; - name = profile_state.insert_or_get(name); - filename = profile_state.insert_or_get(filename); + name = string_storage.insert(name); + filename = string_storage.insert(filename); CodeProvenance::get_instance().add_filename(filename); @@ -73,7 +107,7 @@ Datadog::Sample::push_label(const ExportLabelKey key, std::string_view val) } // Otherwise, persist the val string and add the label - val = profile_state.insert_or_get(val); + val = string_storage.insert(val); auto& label = labels.emplace_back(); label.key = to_slice(key_sv); label.str = to_slice(val); @@ -106,6 +140,7 @@ Datadog::Sample::clear_buffers() labels.clear(); locations.clear(); dropped_frames = 0; + string_storage.reset(); } bool diff --git a/releasenotes/notes/profiling-fix-unbounded-string-buffer-d18056263f3e4bdc.yaml b/releasenotes/notes/profiling-fix-unbounded-string-buffer-d18056263f3e4bdc.yaml new file mode 100644 index 00000000000..bc87b0c87c5 --- /dev/null +++ b/releasenotes/notes/profiling-fix-unbounded-string-buffer-d18056263f3e4bdc.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + profiling: fix unbounded memory usage growth caused by keeping arbitrary + user-generated strings (e.g. asyncio Task names) in an internal table and + never removing them. From 7320b6f04511468c9cf4b8da2fda19bd057d82a0 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:28:40 +0000 Subject: [PATCH 262/372] feat(ci_visibility): support selenium in test visibility (#11610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces support for Selenium, including integration with the RUM product for session recording/replays (requested in #10203 ). A new `hatch` environment is added which tests support for the feature in both `v1` and `v2` versions of the `pytest` plugin, with a matching addition to test suite and specs. A few other changes are added: - properly adding the `test.type` tag when using the test API (used by manual test runners and the new pytest plugin) - adding the `type` tag when the test span is originally created (so that it may be accessed by the Selenium integration) - telemetry for events created/finished for tests is being split out into its own function because the multipurpose single function was becoming unreasonably branch-y with the addition of yet another set of tags ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Vítor De Araújo --- .github/CODEOWNERS | 7 +- ddtrace/_monkey.py | 1 + ddtrace/contrib/internal/selenium/patch.py | 185 +++ ddtrace/contrib/selenium/__init__.py | 37 + ddtrace/internal/ci_visibility/api/_base.py | 5 +- ddtrace/internal/ci_visibility/api/_test.py | 35 +- .../ci_visibility/telemetry/events.py | 119 +- docs/configuration.rst | 11 + hatch.toml | 30 + ...selenium_rum_support-f1b81d0de8820aaa.yaml | 4 + riotfile.py | 2 +- tests/ci_visibility/suitespec.yml | 18 + .../rum_disabled/page_1.html | 29 + .../rum_disabled/page_2.html | 16 + .../static_test_pages/rum_enabled/page_1.html | 36 + .../static_test_pages/rum_enabled/page_2.html | 23 + .../contrib/selenium/test_selenium_chrome.py | 227 ++++ ...ers.test_manual_api_fake_atr_mix_fail.json | 1066 +++++++++-------- ...ers.test_manual_api_fake_atr_mix_pass.json | 571 ++++----- ...ers.test_manual_api_fake_efd_all_pass.json | 804 +++++++------ ...st_manual_api_fake_efd_faulty_session.json | 489 ++++---- ...ers.test_manual_api_fake_efd_mix_fail.json | 806 +++++++------ ...ers.test_manual_api_fake_efd_mix_pass.json | 806 +++++++------ ....test_manual_api_fake_runner_all_fail.json | 258 ++-- ..._fake_runner_all_itr_skip_suite_level.json | 246 ++-- ...i_fake_runner_all_itr_skip_test_level.json | 246 ++-- ....test_manual_api_fake_runner_all_pass.json | 246 ++-- ....test_manual_api_fake_runner_all_skip.json | 246 ++-- ....test_manual_api_fake_runner_mix_fail.json | 631 +++++----- ..._fake_runner_mix_fail_itr_suite_level.json | 839 ++++++------- ...i_fake_runner_mix_fail_itr_test_level.json | 839 ++++++------- ....test_manual_api_fake_runner_mix_pass.json | 421 +++---- ...t_selenium_chrome_pytest_rum_disabled.json | 187 +++ ...st_selenium_chrome_pytest_rum_enabled.json | 188 +++ ...unpatch_does_not_record_selenium_tags.json | 182 +++ ...v2.test_pytest_will_include_lines_pct.json | 83 +- ...v2.test_pytest_with_ddtrace_patch_all.json | 97 +- ...ont_include_lines_pct_if_report_empty.json | 83 +- 38 files changed, 5823 insertions(+), 4296 deletions(-) create mode 100644 ddtrace/contrib/internal/selenium/patch.py create mode 100644 ddtrace/contrib/selenium/__init__.py create mode 100644 releasenotes/notes/ci_visibility-feat-selenium_rum_support-f1b81d0de8820aaa.yaml create mode 100644 tests/contrib/selenium/static_test_pages/rum_disabled/page_1.html create mode 100644 tests/contrib/selenium/static_test_pages/rum_disabled/page_2.html create mode 100644 tests/contrib/selenium/static_test_pages/rum_enabled/page_1.html create mode 100644 tests/contrib/selenium/static_test_pages/rum_enabled/page_2.html create mode 100644 tests/contrib/selenium/test_selenium_chrome.py create mode 100644 tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json create mode 100644 tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json create mode 100644 tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b673cc34a94..d281fe80148 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -51,7 +51,7 @@ tests/opentracer @DataDog/apm-core-python tests/runtime @DataDog/apm-core-python tests/tracer @DataDog/apm-core-python -# CI App and related +# Test Visibility and related ddtrace/contrib/asynctest @DataDog/ci-app-libraries ddtrace/contrib/coverage @DataDog/ci-app-libraries ddtrace/contrib/pytest @DataDog/ci-app-libraries @@ -81,6 +81,11 @@ scripts/ci_visibility/* @DataDog/ci-app-libraries ddtrace/contrib/freezegun @DataDog/ci-app-libraries ddtrace/contrib/internal/freezegun @DataDog/ci-app-libraries tests/contrib/freezegun @DataDog/ci-app-libraries +# Test Visibility: Selenium integration +ddtrace/contrib/selenium @DataDog/ci-app-libraries +ddtrace/internal/selenium @DataDog/ci-app-libraries +tests/contrib/selenium @DataDog/ci-app-libraries +tests/snapshots/test_selenium_* @DataDog/ci-app-libraries # Debugger ddtrace/debugging/ @DataDog/debugger-python diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 61c1ac3770b..b0c17213130 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -101,6 +101,7 @@ "subprocess": True, "unittest": True, "coverage": False, + "selenium": True, } diff --git a/ddtrace/contrib/internal/selenium/patch.py b/ddtrace/contrib/internal/selenium/patch.py new file mode 100644 index 00000000000..5e8d2ed87bc --- /dev/null +++ b/ddtrace/contrib/internal/selenium/patch.py @@ -0,0 +1,185 @@ +import os +import time +import typing as t + +from wrapt.importer import when_imported + +from ddtrace import config +from ddtrace.internal.logger import get_logger +from ddtrace.internal.wrapping.context import WrappingContext +import ddtrace.tracer + + +if t.TYPE_CHECKING: + import selenium.webdriver.remote.webdriver + +log = get_logger(__name__) + +T = t.TypeVar("T") + +_RUM_STOP_SESSION_SCRIPT = """ +if (window.DD_RUM && window.DD_RUM.stopSession) { + window.DD_RUM.stopSession(); + return true; +} else { + return false; +} +""" + +_DEFAULT_FLUSH_SLEEP_MS = 500 + + +def _get_flush_sleep_ms() -> int: + env_flush_sleep_ms = os.getenv("DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS") + if env_flush_sleep_ms is None: + return _DEFAULT_FLUSH_SLEEP_MS + + try: + return int(env_flush_sleep_ms) + except Exception: # noqa E722 + log.warning( + "Could not convert DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS value %s to int, using default: %s", + env_flush_sleep_ms, + _DEFAULT_FLUSH_SLEEP_MS, + ) + return _DEFAULT_FLUSH_SLEEP_MS + + +config._add( + "selenium", + dict(flush_sleep_ms=_get_flush_sleep_ms()), +) + + +class SeleniumWrappingContextBase(WrappingContext): + def _handle_enter(self) -> None: + pass + + def _handle_return(self) -> None: + pass + + def _get_webdriver_instance(self) -> "selenium.webdriver.remote.webdriver.WebDriver": + try: + return self.get_local("self") + except KeyError: + log.debug("Could not get Selenium WebDriver instance") + return None + + def __enter__(self) -> "SeleniumWrappingContextBase": + super().__enter__() + + try: + self._handle_enter() + except Exception: # noqa: E722 + log.debug("Error handling selenium instrumentation enter", exc_info=True) + + return self + + def __return__(self, value: T) -> T: + """Always return the original value no matter what our instrumentation does""" + try: + self._handle_return() + except Exception: # noqa: E722 + log.debug("Error handling instrumentation return", exc_info=True) + + return value + + +class SeleniumGetWrappingContext(SeleniumWrappingContextBase): + def _handle_return(self) -> None: + root_span = ddtrace.tracer.current_root_span() + test_trace_id = root_span.trace_id + + if root_span is None or root_span.get_tag("type") != "test": + return + + webdriver_instance = self._get_webdriver_instance() + + if webdriver_instance is None: + return + + # The trace IDs for Test Visibility data using the CIVisibility protocol are 64-bit + # TODO[ci_visibility]: properly identify whether to use 64 or 128 bit trace_ids + trace_id_64bit = test_trace_id % 2**64 + + webdriver_instance.add_cookie({"name": "datadog-ci-visibility-test-execution-id", "value": str(trace_id_64bit)}) + + root_span.set_tag("test.is_browser", "true") + root_span.set_tag("test.browser.driver", "selenium") + root_span.set_tag("test.browser.driver_version", get_version()) + + # Submit empty values for browser names or version if multiple are found + browser_name = webdriver_instance.capabilities.get("browserName") + browser_version = webdriver_instance.capabilities.get("browserVersion") + + existing_browser_name = root_span.get_tag("test.browser.name") + if existing_browser_name is None: + root_span.set_tag("test.browser.name", browser_name) + elif existing_browser_name not in ["", browser_name]: + root_span.set_tag("test.browser.name", "") + + existing_browser_version = root_span.get_tag("test.browser.version") + if existing_browser_version is None: + root_span.set_tag("test.browser.version", browser_version) + elif existing_browser_version not in ["", browser_version]: + root_span.set_tag("test.browser.version", "") + + +class SeleniumQuitWrappingContext(SeleniumWrappingContextBase): + def _handle_enter(self) -> None: + root_span = ddtrace.tracer.current_root_span() + + if root_span is None or root_span.get_tag("type") != "test": + return + + webdriver_instance = self._get_webdriver_instance() + + if webdriver_instance is None: + return + + is_rum_active = webdriver_instance.execute_script(_RUM_STOP_SESSION_SCRIPT) + time.sleep(config.selenium.flush_sleep_ms / 1000) + + if is_rum_active: + root_span.set_tag("test.is_rum_active", "true") + + webdriver_instance.delete_cookie("datadog-ci-visibility-test-execution-id") + + +def get_version() -> str: + import selenium + + try: + return selenium.__version__ + except AttributeError: + log.debug("Could not get Selenium version") + return "" + + +def patch() -> None: + import selenium + + if getattr(selenium, "_datadog_patch", False): + return + + @when_imported("selenium.webdriver.remote.webdriver") + def _(m): + SeleniumGetWrappingContext(m.WebDriver.get).wrap() + SeleniumQuitWrappingContext(m.WebDriver.quit).wrap() + SeleniumQuitWrappingContext(m.WebDriver.close).wrap() + + selenium._datadog_patch = True + + +def unpatch() -> None: + import selenium + from selenium.webdriver.remote.webdriver import WebDriver + + if not getattr(selenium, "_datadog_patch", False): + return + + SeleniumGetWrappingContext.extract(WebDriver.get).unwrap() + SeleniumQuitWrappingContext.extract(WebDriver.quit).unwrap() + SeleniumQuitWrappingContext.extract(WebDriver.close).unwrap() + + selenium._datadog_patch = False diff --git a/ddtrace/contrib/selenium/__init__.py b/ddtrace/contrib/selenium/__init__.py new file mode 100644 index 00000000000..d30c896fdd7 --- /dev/null +++ b/ddtrace/contrib/selenium/__init__.py @@ -0,0 +1,37 @@ +""" +The Selenium integration enriches Test Visibility data with extra tags and, if available, +Real User Monitoring session replays. + +Enabling +~~~~~~~~ + +The Selenium integration is enabled by default in test contexts (eg: pytest, or unittest). Use +:func:`patch()` to enable the integration:: + + from ddtrace import patch + patch(selenium=True) + + +When using pytest, the `--ddtrace-patch-all` flag is required in order for this integration to +be enabled. + +Configuration +~~~~~~~~~~~~~ + +The Selenium integration can be configured using the following options: + +DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: The time in milliseconds to wait after flushing the RUM session. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["selenium"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + # Expose public methods + from ..internal.selenium.patch import get_version + from ..internal.selenium.patch import patch + from ..internal.selenium.patch import unpatch + + __all__ = ["get_version", "patch", "unpatch"] diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index fd27c7b6a9f..dbaa48d1af3 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -183,6 +183,9 @@ def _start_span(self) -> None: span_type=SpanTypes.TEST, activate=True, ) + # Setting initial tags is necessary for integrations that might look at the span before it is finished + self._span.set_tag(EVENT_TYPE, self._event_type) + self._span.set_tag(SPAN_KIND, "test") log.debug("Started span %s for item %s", self._span, self) @_require_span @@ -219,8 +222,6 @@ def _set_default_tags(self) -> None: self.set_tags( { - EVENT_TYPE: self._event_type, - SPAN_KIND: "test", COMPONENT: self._session_settings.test_framework, test.FRAMEWORK: self._session_settings.test_framework, test.FRAMEWORK_VERSION: self._session_settings.test_framework_version, diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index dab6d3e0be6..73dc6397b63 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -5,6 +5,7 @@ from typing import Optional from typing import Union +from ddtrace.ext import SpanTypes from ddtrace.ext import test from ddtrace.ext.test_visibility import ITR_SKIPPING_LEVEL from ddtrace.ext.test_visibility._item_ids import TestId @@ -21,8 +22,8 @@ from ddtrace.internal.ci_visibility.constants import TEST_IS_NEW from ddtrace.internal.ci_visibility.constants import TEST_IS_RETRY from ddtrace.internal.ci_visibility.telemetry.constants import EVENT_TYPES -from ddtrace.internal.ci_visibility.telemetry.events import record_event_created -from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished +from ddtrace.internal.ci_visibility.telemetry.events import record_event_created_test +from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished_test from ddtrace.internal.logger import get_logger from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -119,20 +120,20 @@ def _set_span_tags(self) -> None: self._span.set_exc_info(self._exc_info.exc_type, self._exc_info.exc_value, self._exc_info.exc_traceback) def _telemetry_record_event_created(self): - record_event_created( - event_type=self._event_type_metric_name, + record_event_created_test( test_framework=self._session_settings.test_framework_metric_name, - is_benchmark=self._is_benchmark if self._is_benchmark is not None else None, + is_benchmark=self._is_benchmark, ) def _telemetry_record_event_finished(self): - record_event_finished( - event_type=self._event_type_metric_name, + record_event_finished_test( test_framework=self._session_settings.test_framework_metric_name, - is_benchmark=self._is_benchmark if self._is_benchmark is not None else None, - is_new=self._is_new if self._is_new is not None else None, + is_benchmark=self._is_benchmark, + is_new=self.is_new(), is_retry=self._efd_is_retry or self._atr_is_retry, early_flake_detection_abort_reason=self._efd_abort_reason, + is_rum=self._is_rum(), + browser_driver=self._get_browser_driver(), ) def finish_test( @@ -143,6 +144,9 @@ def finish_test( override_finish_time: Optional[float] = None, ) -> None: log.debug("Test Visibility: finishing %s, with status: %s, reason: %s", self, status, reason) + + self.set_tag(test.TYPE, SpanTypes.TEST) + if status is not None: self.set_status(status) if reason is not None: @@ -379,3 +383,16 @@ def atr_get_final_status(self) -> TestStatus: return TestStatus.PASS return TestStatus.FAIL + + # + # Selenium / RUM functionality + # + def _is_rum(self): + if self._span is None: + return False + return self._span.get_tag("is_rum_active") == "true" + + def _get_browser_driver(self): + if self._span is None: + return None + return self._span.get_tag("test.browser.driver") diff --git a/ddtrace/internal/ci_visibility/telemetry/events.py b/ddtrace/internal/ci_visibility/telemetry/events.py index bdaee8a9384..be39c8079cf 100644 --- a/ddtrace/internal/ci_visibility/telemetry/events.py +++ b/ddtrace/internal/ci_visibility/telemetry/events.py @@ -26,9 +26,6 @@ def _record_event( test_framework: Optional[TEST_FRAMEWORKS], has_codeowners: Optional[bool] = False, is_unsupported_ci: Optional[bool] = False, - is_benchmark: Optional[bool] = False, - is_new: Optional[bool] = False, - is_retry: Optional[bool] = False, early_flake_detection_abort_reason: Optional[str] = None, ): log.debug( @@ -37,37 +34,25 @@ def _record_event( ", test_framework=%s" ", has_codeowners=%s" ", is_unsuported_ci=%s" - ", is_benchmark=%s" - ", is_new=%s" - ", is_retry=%s" ", early_flake_detection_abort_reason=%s", event, event_type, test_framework, has_codeowners, is_unsupported_ci, - is_benchmark, - is_new, - is_retry, early_flake_detection_abort_reason, ) + if event_type == EVENT_TYPES.TEST: + log.warning("Test events should be recorded with record_event_test_created or record_event_test_finished") + return if has_codeowners and event_type != EVENT_TYPES.SESSION: log.debug("has_codeowners tag can only be set for sessions, but event type is %s", event_type) if is_unsupported_ci and event_type != EVENT_TYPES.SESSION: log.debug("unsupported_ci tag can only be set for sessions, but event type is %s", event_type) - if is_benchmark and event_type != EVENT_TYPES.TEST: - log.debug("is_benchmark tag can only be set for tests, but event type is %s", event_type) - if is_new and not (event_type == EVENT_TYPES.TEST and event == EVENTS_TELEMETRY.FINISHED): - log.debug( - "is_new tag can only be set for test finishes, but event type is %s and event is %s", event_type, event - ) - if is_retry and not (event_type == EVENT_TYPES.TEST and event == EVENTS_TELEMETRY.FINISHED): - log.debug( - "is_retry tag can only be set for test finishes, but event type is %s and event is %s", event_type, event - ) + if early_flake_detection_abort_reason and ( - event_type not in [EVENT_TYPES.TEST, EVENT_TYPES.SESSION] or event != EVENTS_TELEMETRY.FINISHED + event_type not in [EVENT_TYPES.SESSION] or event != EVENTS_TELEMETRY.FINISHED ): log.debug( "early_flake_detection_abort_reason tag can only be set for tests and session finish events", @@ -80,19 +65,7 @@ def _record_event( _tags.append(("has_codeowners", "1" if has_codeowners else "0")) _tags.append(("is_unsupported_ci", "1" if has_codeowners else "0")) - if event_type == EVENT_TYPES.TEST: - _tags.append(("is_benchmark", "1" if is_benchmark else "0")) - if event == EVENTS_TELEMETRY.FINISHED: - if is_new: - _tags.append(("is_new", "true")) - if is_retry: - _tags.append(("is_retry", "true")) - - if ( - early_flake_detection_abort_reason - and event == EVENTS_TELEMETRY.FINISHED - and event_type in [EVENT_TYPES.TEST, EVENT_TYPES.SESSION] - ): + if early_flake_detection_abort_reason and event == EVENTS_TELEMETRY.FINISHED and event_type == EVENT_TYPES.SESSION: _tags.append(("early_flake_detection_abort_reason", early_flake_detection_abort_reason)) telemetry_writer.add_count_metric(_NAMESPACE, event.value, 1, tuple(_tags)) @@ -103,8 +76,11 @@ def record_event_created( test_framework: TEST_FRAMEWORKS, has_codeowners: Optional[bool] = None, is_unsupported_ci: Optional[bool] = None, - is_benchmark: Optional[bool] = False, ): + if event_type == EVENT_TYPES.TEST: + log.warning("Test events should be recorded with record_event_test_created") + return + if test_framework == TEST_FRAMEWORKS.MANUAL: # manual API usage is tracked only by way of tracking created events record_manual_api_event_created(event_type) @@ -115,7 +91,6 @@ def record_event_created( test_framework=test_framework, has_codeowners=has_codeowners, is_unsupported_ci=is_unsupported_ci, - is_benchmark=is_benchmark, ) @@ -124,20 +99,18 @@ def record_event_finished( test_framework: Optional[TEST_FRAMEWORKS], has_codeowners: bool = False, is_unsupported_ci: bool = False, - is_benchmark: bool = False, - is_new: bool = False, - is_retry: bool = False, early_flake_detection_abort_reason: Optional[str] = None, ): + if event_type == EVENT_TYPES.TEST: + log.warning("Test events should be recorded with record_event_test_finished") + return + _record_event( event=EVENTS_TELEMETRY.FINISHED, event_type=event_type, test_framework=test_framework, has_codeowners=has_codeowners, is_unsupported_ci=is_unsupported_ci, - is_benchmark=is_benchmark, - is_new=is_new, - is_retry=is_retry, early_flake_detection_abort_reason=early_flake_detection_abort_reason, ) @@ -150,3 +123,67 @@ def record_manual_api_event_created(event_type: EVENT_TYPES): def record_events_enqueued_for_serialization(events_count: int): telemetry_writer.add_count_metric(_NAMESPACE, EVENTS_TELEMETRY.ENQUEUED_FOR_SERIALIZATION, events_count) + + +def record_event_created_test( + test_framework: Optional[TEST_FRAMEWORKS], + is_benchmark: bool = False, +): + log.debug("Recording test event created: test_framework=%s, is_benchmark=%s", test_framework, is_benchmark) + tags: List[Tuple[str, str]] = [("event_type", EVENT_TYPES.TEST)] + + if test_framework and test_framework != TEST_FRAMEWORKS.MANUAL: + tags.append(("test_framework", str(test_framework.value))) + elif test_framework == TEST_FRAMEWORKS.MANUAL: + record_manual_api_event_created(EVENT_TYPES.TEST) + + if is_benchmark: + tags.append(("is_benchmark", "true")) + + telemetry_writer.add_count_metric(_NAMESPACE, EVENTS_TELEMETRY.FINISHED, 1, tuple(tags)) + + +def record_event_finished_test( + test_framework: Optional[TEST_FRAMEWORKS], + is_new: bool = False, + is_retry: bool = False, + early_flake_detection_abort_reason: Optional[str] = None, + is_rum: bool = False, + browser_driver: Optional[str] = None, + is_benchmark: bool = False, +): + log.debug( + "Recording test event finished: test_framework=%s" + ", is_new=%s" + ", is_retry=%s" + ", early_flake_detection_abort_reason=%s" + ", is_rum=%s" + ", browser_driver=%s" + ", is_benchmark=%s", + test_framework, + is_new, + is_retry, + early_flake_detection_abort_reason, + is_rum, + browser_driver, + is_benchmark, + ) + + tags: List[Tuple[str, str]] = [("event_type", EVENT_TYPES.TEST)] + + if test_framework is not None: + tags.append(("test_framework", test_framework)) + if is_benchmark: + tags.append(("is_benchmark", "true")) + if is_new: + tags.append(("is_new", "true")) + if is_retry: + tags.append(("is_retry", "true")) + if is_rum: + tags.append(("is_rum", "true")) + if browser_driver is not None: + tags.append(("browser_driver", browser_driver)) + if early_flake_detection_abort_reason is not None: + tags.append(("early_flake_detection_abort_reason", early_flake_detection_abort_reason)) + + telemetry_writer.add_count_metric(_NAMESPACE, EVENTS_TELEMETRY.FINISHED, 1, tuple(tags)) diff --git a/docs/configuration.rst b/docs/configuration.rst index 1971e092d79..504440348fe 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -597,6 +597,17 @@ Test Visibility version_added: v2.16.0: + DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: + type: Integer + default: 500 + + description: | + Configures how long, in milliseconds, the Selenium integration will wait after invoking the RUM flush function + during calls to the driver's ``quit()`` or ``close()`` methods. This helps ensure that the call to the + asynchronous function finishes before the driver is closed. + + version_added: + v2.19.0: Agent ----- diff --git a/hatch.toml b/hatch.toml index 668a7ef5497..7bc7e107c04 100644 --- a/hatch.toml +++ b/hatch.toml @@ -471,3 +471,33 @@ test = [ [[envs.freezegun.matrix]] python = ["3.7", "3.10", "3.12"] freezegun = ["~=1.3.0", "~=1.5.0"] + +[envs.selenium] +template = "selenium" +dependencies = [ + "pytest", + "pytest-cov", + "hypothesis", + "selenium~=4.0", + "webdriver-manager" +] + +[evs.selenium.env-vars] +DD_AGENT_TRACER_URL = "9126" +CMAKE_BUILD_PARALLEL_LEVEL = "12" + +[envs.selenium.scripts] +test = [ + "pip freeze", + "pytest -c /dev/null --no-ddtrace --no-cov tests/contrib/selenium {args:}", +] + +[[envs.selenium.matrix]] +python = ["3.7", "3.10", "3.12"] +tested_pytest_plugin_version = ["v1", "v2"] + +[envs.selenium.overrides] +matrix.tested_pytest_plugin_version.env-vars = [ + { key = "_TESTED_PYTEST_PLUGIN_VERSION", value = "false", if = ["v1"]}, + { key = "_TESTED_PYTEST_PLUGIN_VERSION", value = "true", if = ["v2"]} +] diff --git a/releasenotes/notes/ci_visibility-feat-selenium_rum_support-f1b81d0de8820aaa.yaml b/releasenotes/notes/ci_visibility-feat-selenium_rum_support-f1b81d0de8820aaa.yaml new file mode 100644 index 00000000000..5174684095d --- /dev/null +++ b/releasenotes/notes/ci_visibility-feat-selenium_rum_support-f1b81d0de8820aaa.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + CI Visibility: Adds support for `Selenium and RUM integration `_ diff --git a/riotfile.py b/riotfile.py index f25e09e2d25..1a1f65ed116 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1605,7 +1605,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, env={ "DD_AGENT_PORT": "9126", - "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", }, venvs=[ Venv( diff --git a/tests/ci_visibility/suitespec.yml b/tests/ci_visibility/suitespec.yml index fe69f732c6d..29317ba3294 100644 --- a/tests/ci_visibility/suitespec.yml +++ b/tests/ci_visibility/suitespec.yml @@ -10,6 +10,9 @@ components: - ddtrace/contrib/pytest/* - ddtrace/contrib/pytest_bdd/* - ddtrace/contrib/pytest_benchmark/* + selenium: + - ddtrace/contrib/selenium/* + - ddtrace/contrib/internal/selenium/* unittest: - ddtrace/contrib/unittest/* suites: @@ -70,6 +73,21 @@ suites: pattern: pytest_plugin_v2 runner: hatch snapshot: true + selenium: + parallelism: 4 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@ci_visibility' + - '@pytest' + - '@unittest' + - '@selenium' + - tests/contrib/selenium/* + - tests/snapshots/test_selenium* + runner: hatch + snapshot: true unittest: paths: - '@contrib' diff --git a/tests/contrib/selenium/static_test_pages/rum_disabled/page_1.html b/tests/contrib/selenium/static_test_pages/rum_disabled/page_1.html new file mode 100644 index 00000000000..009a6634905 --- /dev/null +++ b/tests/contrib/selenium/static_test_pages/rum_disabled/page_1.html @@ -0,0 +1,29 @@ + + + + Page 1 + + + + + + + +

This is page 1!

+ + + + + + + + + + + + + +
PageLink
1Page 1
2Page 2
+ + + diff --git a/tests/contrib/selenium/static_test_pages/rum_disabled/page_2.html b/tests/contrib/selenium/static_test_pages/rum_disabled/page_2.html new file mode 100644 index 00000000000..96d0d4ac631 --- /dev/null +++ b/tests/contrib/selenium/static_test_pages/rum_disabled/page_2.html @@ -0,0 +1,16 @@ + + + + Page 2 + + + + + + + +

This is page 2!

+Back to page 1. + + + diff --git a/tests/contrib/selenium/static_test_pages/rum_enabled/page_1.html b/tests/contrib/selenium/static_test_pages/rum_enabled/page_1.html new file mode 100644 index 00000000000..1c2c3017292 --- /dev/null +++ b/tests/contrib/selenium/static_test_pages/rum_enabled/page_1.html @@ -0,0 +1,36 @@ + + + + Page 1 + + + + + + + + +

This is page 1!

+ + + + + + + + + + + + + +
PageLink
1Page 1
2Page 2
+ + + diff --git a/tests/contrib/selenium/static_test_pages/rum_enabled/page_2.html b/tests/contrib/selenium/static_test_pages/rum_enabled/page_2.html new file mode 100644 index 00000000000..e46f0043f34 --- /dev/null +++ b/tests/contrib/selenium/static_test_pages/rum_enabled/page_2.html @@ -0,0 +1,23 @@ + + + + Page 2 + + + + + + + + +

This is page 2!

+Back to page 1. + + + diff --git a/tests/contrib/selenium/test_selenium_chrome.py b/tests/contrib/selenium/test_selenium_chrome.py new file mode 100644 index 00000000000..d8b68a8fa53 --- /dev/null +++ b/tests/contrib/selenium/test_selenium_chrome.py @@ -0,0 +1,227 @@ +"""Tests for selenium + RUM integration + +IMPORTANT NOTE: these tests only reliably work on Linux/x86_64 due to some annoyingly picky issues with installing +Selenium and a working browser/webdriver combination on non-x86_64 architectures (at time of writing, at least, +Selenium's webdriver-manager doesn't support Linux on non-x86_64). +""" +import http.server +import multiprocessing +import os +from pathlib import Path +import platform +import socketserver +import subprocess +import textwrap + +import pytest + +from tests.ci_visibility.util import _get_default_ci_env_vars +from tests.utils import snapshot + + +SELENIUM_SNAPSHOT_IGNORES = [ + "resource", # Ignored because v1 and v2 plugins have different results, but that's okay + "meta.ci.workspace_path", + "meta.error.stack", + "meta.library_version", + "meta.os.architecture", + "meta.os.platform", + "meta.os.version", + "meta.runtime-id", + "meta.runtime.version", + "meta.test.browser.version", # ignored because it may change when images are rebuilt + "meta.test.browser.driver_version", # ignored because it may change when images are rebuilt + "meta.test.framework_version", + "meta.test_module_id", + "meta.test_session_id", + "meta.test_suite_id", + "metrics._dd.top_level", + "metrics._dd.tracer_kr", + "metrics._sampling_priority_v1", + "metrics.process_id", + "duration", + "start", +] + + +@pytest.fixture +def _http_server(scope="function"): + """Provides a simple HTTP server that servers the pages to be browser + + We use an HTTP server because RUM does not work with file:// URLs (it's unable to establish session storage) + """ + + def _run_server(): + server_root = Path(__file__).parent / "static_test_pages" + os.chdir(server_root) + # We do not use the context manager gecause we need allow_reuse_address + httpd = socketserver.TCPServer( + ("localhost", 8079), http.server.SimpleHTTPRequestHandler, bind_and_activate=False + ) + httpd.allow_reuse_address = True + httpd.daemon_threads = True + httpd.server_bind() + httpd.server_activate() + httpd.serve_forever() + + server = multiprocessing.Process(target=_run_server) + server.start() + yield + if server.is_alive(): + server.terminate() + + +@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) +@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") +def test_selenium_chrome_pytest_rum_enabled(_http_server, testdir, git_repo): + selenium_test_script = textwrap.dedent( + """ + from pathlib import Path + + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.chrome.options import Options + + def test_selenium_local_pass(): + options = Options() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + + with webdriver.Chrome(options=options) as driver: + url = "http://localhost:8079/rum_enabled/page_1.html" + + driver.get(url) + + assert driver.title == "Page 1" + + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + + link_2.click() + + assert driver.title == "Page 2" + + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() + + assert driver.title == "Page 1" + """ + ) + testdir.makepyfile(test_selenium=selenium_test_script) + subprocess.run( + ["pytest", "--ddtrace", "-s", "--ddtrace-patch-all"], + env=_get_default_ci_env_vars( + dict( + DD_API_KEY="foobar.baz", + DD_CIVISIBILITY_ITR_ENABLED="false", + DD_PATCH_MODULES="sqlite3:false", + CI_PROJECT_DIR=str(testdir.tmpdir), + DD_CIVISIBILITY_AGENTLESS_ENABLED="false", + DD_PYTEST_USE_NEW_PLUGIN_BETA=os.environ.get("_TESTED_PYTEST_PLUGIN_VERSION"), + ) + ), + ) + + +@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) +@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") +def test_selenium_chrome_pytest_rum_disabled(_http_server, testdir, git_repo): + selenium_test_script = textwrap.dedent( + """ + from pathlib import Path + + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.chrome.options import Options + + def test_selenium_local_pass(): + options = Options() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + + with webdriver.Chrome(options=options) as driver: + url = "http://localhost:8079/rum_disabled/page_1.html" + + driver.get(url) + + assert driver.title == "Page 1" + + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + + link_2.click() + + assert driver.title == "Page 2" + + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() + + assert driver.title == "Page 1" + """ + ) + testdir.makepyfile(test_selenium=selenium_test_script) + subprocess.run( + ["pytest", "--ddtrace", "-s", "--ddtrace-patch-all"], + env=_get_default_ci_env_vars( + dict( + DD_API_KEY="foobar.baz", + DD_CIVISIBILITY_ITR_ENABLED="false", + DD_PATCH_MODULES="sqlite3:false", + CI_PROJECT_DIR=str(testdir.tmpdir), + DD_CIVISIBILITY_AGENTLESS_ENABLED="false", + DD_PYTEST_USE_NEW_PLUGIN_BETA=os.environ.get("_TESTED_PYTEST_PLUGIN_VERSION"), + ) + ), + ) + + +@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) +@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") +def test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags(_http_server, testdir, git_repo): + selenium_test_script = textwrap.dedent( + """ + from pathlib import Path + + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.chrome.options import Options + + from ddtrace.contrib.selenium import unpatch + + def test_selenium_local_unpatch(): + unpatch() + options = Options() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + + with webdriver.Chrome(options=options) as driver: + url = "http://localhost:8079/rum_disabled/page_1.html" + + driver.get(url) + + assert driver.title == "Page 1" + + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + + link_2.click() + + assert driver.title == "Page 2" + + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() + + assert driver.title == "Page 1" + """ + ) + testdir.makepyfile(test_selenium=selenium_test_script) + subprocess.run( + ["pytest", "--ddtrace", "-s", "--ddtrace-patch-all"], + env=_get_default_ci_env_vars( + dict( + DD_API_KEY="foobar.baz", + DD_CIVISIBILITY_ITR_ENABLED="false", + DD_PATCH_MODULES="sqlite3:false", + CI_PROJECT_DIR=str(testdir.tmpdir), + DD_CIVISIBILITY_AGENTLESS_ENABLED="false", + DD_PYTEST_USE_NEW_PLUGIN_BETA=os.environ.get("_TESTED_PYTEST_PLUGIN_VERSION"), + ) + ), + ) diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json index ebce28ebf48..b3c184cc7a8 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_fail.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -52,9 +52,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -62,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 77000, - "start": 1730382044807671634 + "duration": 84792, + "start": 1733391686094613959 }], [ { @@ -80,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -95,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -106,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -124,9 +125,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -134,12 +136,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 109958, - "start": 1730382044822965176 + "duration": 98667, + "start": 1733391686109850459 }], [ { @@ -152,11 +154,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -167,7 +169,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -178,13 +180,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -196,9 +198,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -206,12 +209,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 68542, - "start": 1730382044823201342 + "duration": 51000, + "start": 1733391686110055793 }], [ { @@ -224,11 +227,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -239,7 +242,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -250,13 +253,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -268,9 +271,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -278,12 +282,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 85208, - "start": 1730382044823359676 + "duration": 54709, + "start": 1733391686110191709 }], [ { @@ -296,11 +300,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -311,7 +315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -322,13 +326,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -340,9 +344,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -350,12 +355,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 57750, - "start": 1730382044823556884 + "duration": 47333, + "start": 1733391686110323418 }], [ { @@ -368,11 +373,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -383,7 +388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -394,13 +399,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -412,9 +417,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -422,12 +428,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 56375, - "start": 1730382044823719217 + "duration": 43875, + "start": 1733391686110443418 }], [ { @@ -440,11 +446,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -455,7 +461,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -466,13 +472,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -484,9 +490,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -494,12 +501,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47375, - "start": 1730382044823861717 + "duration": 42000, + "start": 1733391686110561543 }], [ { @@ -512,11 +519,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -527,7 +534,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -538,13 +545,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -556,9 +563,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -566,12 +574,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48041, - "start": 1730382044823994051 + "duration": 43625, + "start": 1733391686110675834 }], [ { @@ -584,11 +592,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -599,7 +607,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -610,13 +618,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -626,9 +634,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -636,10 +645,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 34500, - "start": 1730382044824125717 + "duration": 33708, + "start": 1733391686110793751 }], [ { @@ -652,11 +661,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -667,7 +676,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -678,13 +687,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -695,9 +704,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -705,10 +715,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 35000, - "start": 1730382044824240176 + "duration": 42167, + "start": 1733391686110914501 }], [ { @@ -721,11 +731,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -736,7 +746,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -747,13 +757,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -764,9 +774,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -774,10 +785,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 33916, - "start": 1730382044824352301 + "duration": 34708, + "start": 1733391686111034001 }], [ { @@ -790,11 +801,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -805,7 +816,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -816,13 +827,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -833,9 +844,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -843,10 +855,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 108042, - "start": 1730382044824463217 + "duration": 41583, + "start": 1733391686111150626 }], [ { @@ -859,11 +871,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -874,7 +886,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -885,13 +897,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -902,9 +914,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -912,10 +925,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 44583, - "start": 1730382044824735634 + "duration": 39167, + "start": 1733391686111261959 }], [ { @@ -928,11 +941,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -943,7 +956,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -954,13 +967,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -971,9 +984,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -981,10 +995,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 37291, - "start": 1730382044824865551 + "duration": 30750, + "start": 1733391686111371043 }], [ { @@ -997,11 +1011,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1012,7 +1026,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1023,13 +1037,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1040,9 +1054,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1050,10 +1065,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 35083, - "start": 1730382044824988551 + "duration": 33958, + "start": 1733391686111475543 }], [ { @@ -1066,11 +1081,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1081,7 +1096,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1092,13 +1107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1109,9 +1124,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1119,10 +1135,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 34459, - "start": 1730382044825100842 + "duration": 31166, + "start": 1733391686111588043 }], [ { @@ -1135,11 +1151,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1150,7 +1166,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1161,13 +1177,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_atr_mix_fail", @@ -1179,9 +1195,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1189,12 +1206,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 12, "test.source.start": 4 }, - "duration": 66167, - "start": 1730382044825214592 + "duration": 66292, + "start": 1733391686111690001 }], [ { @@ -1207,11 +1224,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1222,7 +1239,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1233,13 +1250,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1250,9 +1267,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1260,10 +1278,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 33834, - "start": 1730382044825351842 + "duration": 30375, + "start": 1733391686111821668 }], [ { @@ -1276,11 +1294,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1291,7 +1309,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1302,13 +1320,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1319,9 +1337,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1329,10 +1348,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31125, - "start": 1730382044825450842 + "duration": 48583, + "start": 1733391686111917501 }], [ { @@ -1345,11 +1364,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1360,7 +1379,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1371,13 +1390,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1389,9 +1408,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1399,10 +1419,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 32708, - "start": 1730382044825556426 + "duration": 33250, + "start": 1733391686112036251 }], [ { @@ -1415,11 +1435,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1430,7 +1450,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1441,13 +1461,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1459,9 +1479,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1469,10 +1490,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 47250, - "start": 1730382044825664134 + "duration": 33417, + "start": 1733391686112139084 }], [ { @@ -1485,11 +1506,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1500,7 +1521,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1511,13 +1532,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1529,9 +1550,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1539,10 +1561,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31791, - "start": 1730382044825785926 + "duration": 34292, + "start": 1733391686112253709 }], [ { @@ -1555,11 +1577,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1570,7 +1592,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1581,13 +1603,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1599,9 +1621,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1609,10 +1632,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31000, - "start": 1730382044825889467 + "duration": 33250, + "start": 1733391686112356376 }], [ { @@ -1625,11 +1648,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1640,7 +1663,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1651,13 +1674,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1669,9 +1692,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1679,10 +1703,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31625, - "start": 1730382044825991676 + "duration": 72000, + "start": 1733391686112466209 }], [ { @@ -1695,11 +1719,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1710,7 +1734,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1721,13 +1745,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1738,9 +1762,10 @@ "test.parameters": "{\"param1\": \"value3\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test.type": "test", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test" }, "metrics": { @@ -1748,10 +1773,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31458, - "start": 1730382044826095759 + "duration": 33417, + "start": 1733391686112612959 }], [ { @@ -1764,11 +1789,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1779,7 +1804,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1790,13 +1815,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_fail", @@ -1804,7 +1829,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "14120222913796326459", + "test_session_id": "11574621699071202011", "type": "test_session_end" }, "metrics": { @@ -1812,10 +1837,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 20716333, - "start": 1730382044807300259 + "duration": 20504542, + "start": 1733391686094095209 }, { "name": "test_visibility.module", @@ -1827,11 +1852,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1842,7 +1867,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1853,12 +1878,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_fail", @@ -1868,8 +1893,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "fail", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", "type": "test_module_end" }, "metrics": { @@ -1877,8 +1902,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18690542, - "start": 1730382044807618967 + "duration": 18307667, + "start": 1733391686094540251 }, { "name": "test_visibility.suite", @@ -1890,11 +1915,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1905,7 +1930,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1916,12 +1941,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1930,9 +1955,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "13752089970478418748", - "test_session_id": "14120222913796326459", - "test_suite_id": "10844366306027865327", + "test_module_id": "6645356407967337929", + "test_session_id": "11574621699071202011", + "test_suite_id": "552099717724874950", "type": "test_suite_end" }, "metrics": { @@ -1940,8 +1965,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18582042, - "start": 1730382044807643342 + "duration": 18165542, + "start": 1733391686094580001 }, { "name": "test_visibility.module", @@ -1953,11 +1978,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1968,7 +1993,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1979,12 +2004,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_fail", @@ -1994,8 +2019,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "fail", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", "type": "test_module_end" }, "metrics": { @@ -2003,8 +2028,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1523500, - "start": 1730382044826401759 + "duration": 1596917, + "start": 1733391686112909209 }, { "name": "test_visibility.suite", @@ -2016,11 +2041,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2031,7 +2056,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2042,12 +2067,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2056,9 +2081,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test_suite_end" }, "metrics": { @@ -2066,8 +2091,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 890458, - "start": 1730382044826423509 + "duration": 919834, + "start": 1733391686112949584 }, { "name": "test_visibility.suite", @@ -2079,11 +2104,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2094,7 +2119,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2105,12 +2130,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2119,9 +2144,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "12536544582847089510", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "3054063815465848038", "type": "test_suite_end" }, "metrics": { @@ -2129,8 +2154,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 486667, - "start": 1730382044827363842 + "duration": 491292, + "start": 1733391686113939626 }], [ { @@ -2143,11 +2168,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2158,7 +2183,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2169,13 +2194,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2185,9 +2210,10 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2195,10 +2221,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 31125, - "start": 1730382044826444009 + "duration": 33042, + "start": 1733391686112971834 }], [ { @@ -2211,11 +2237,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2226,7 +2252,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2237,13 +2263,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2253,9 +2279,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2263,10 +2290,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 30125, - "start": 1730382044826544509 + "duration": 31667, + "start": 1733391686113079751 }], [ { @@ -2279,11 +2306,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2294,7 +2321,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2305,13 +2332,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2321,9 +2348,10 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2331,10 +2359,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 29917, - "start": 1730382044826639384 + "duration": 31084, + "start": 1733391686113185084 }], [ { @@ -2347,11 +2375,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2362,7 +2390,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2373,13 +2401,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2389,9 +2417,10 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2399,10 +2428,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 29250, - "start": 1730382044826741342 + "duration": 30042, + "start": 1733391686113277334 }], [ { @@ -2415,11 +2444,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2430,7 +2459,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2441,13 +2470,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2457,9 +2486,10 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2467,10 +2497,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 27875, - "start": 1730382044826835134 + "duration": 35625, + "start": 1733391686113369459 }], [ { @@ -2483,11 +2513,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2498,7 +2528,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2509,13 +2539,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2525,9 +2555,10 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2535,10 +2566,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 28459, - "start": 1730382044826925842 + "duration": 29000, + "start": 1733391686113467793 }], [ { @@ -2551,11 +2582,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2566,7 +2597,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2577,13 +2608,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2593,9 +2624,10 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2603,10 +2635,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 28458, - "start": 1730382044827018176 + "duration": 28583, + "start": 1733391686113564918 }], [ { @@ -2619,11 +2651,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2634,7 +2666,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2645,13 +2677,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2661,9 +2693,10 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2671,10 +2704,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 28084, - "start": 1730382044827109592 + "duration": 37958, + "start": 1733391686113661751 }], [ { @@ -2687,11 +2720,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2702,7 +2735,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2713,13 +2746,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2729,9 +2762,10 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "13004843146614843617", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "16905397603701796804", "type": "test" }, "metrics": { @@ -2739,10 +2773,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 27500, - "start": 1730382044827201801 + "duration": 29208, + "start": 1733391686113760376 }], [ { @@ -2755,11 +2789,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2770,7 +2804,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2781,13 +2815,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2798,9 +2832,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "12536544582847089510", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "3054063815465848038", "type": "test" }, "metrics": { @@ -2808,12 +2843,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43833, - "start": 1730382044827384634 + "duration": 55041, + "start": 1733391686113962418 }], [ { @@ -2826,11 +2861,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2841,7 +2876,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2852,13 +2887,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2868,9 +2903,10 @@ "test.name": "m2_s2_t2", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "12536544582847089510", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "3054063815465848038", "type": "test" }, "metrics": { @@ -2878,10 +2914,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 30042, - "start": 1730382044827497717 + "duration": 30000, + "start": 1733391686114087751 }], [ { @@ -2894,11 +2930,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2909,7 +2945,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2920,13 +2956,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_fail", "test.framework": "dd_manual_test_fw", @@ -2937,9 +2973,10 @@ "test.name": "m2_s2_t2", "test.status": "fail", "test.suite": "m2_s2", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "12536544582847089510", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "3054063815465848038", "type": "test" }, "metrics": { @@ -2947,10 +2984,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706 + "process_id": 32371 }, - "duration": 41416, - "start": 1730382044827600426 + "duration": 32875, + "start": 1733391686114195043 }], [ { @@ -2963,11 +3000,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_fail0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388dc00000000", + "_dd.p.tid": "6751754600000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2978,7 +3015,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2989,13 +3026,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "99a84bd0e2a2496ca205d0714eb1d4c3", + "runtime-id": "a86bdb8f59674cdd8311aafa8e281fdc", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_atr_mix_fail", @@ -3007,9 +3044,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "12283267209251700888", - "test_session_id": "14120222913796326459", - "test_suite_id": "12536544582847089510", + "test.type": "test", + "test_module_id": "9230878614676945450", + "test_session_id": "11574621699071202011", + "test_suite_id": "3054063815465848038", "type": "test" }, "metrics": { @@ -3017,10 +3055,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80706, + "process_id": 32371, "test.source.end": 12, "test.source.start": 4 }, - "duration": 46333, - "start": 1730382044827721176 + "duration": 49667, + "start": 1733391686114292834 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json index 3bd4c8bc048..473afff13c1 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_atr_mix_pass.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -52,9 +52,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -62,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 76625, - "start": 1730382048418891386 + "duration": 113792, + "start": 1733391672219881467 }], [ { @@ -80,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -95,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -106,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -124,9 +125,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -134,12 +136,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 107625, - "start": 1730382048438090552 + "duration": 99709, + "start": 1733391672235181050 }], [ { @@ -152,11 +154,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -167,7 +169,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -178,13 +180,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -196,9 +198,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -206,12 +209,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 59000, - "start": 1730382048438325302 + "duration": 55750, + "start": 1733391672235395800 }], [ { @@ -224,11 +227,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -239,7 +242,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -250,13 +253,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -268,9 +271,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -278,12 +282,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 49000, - "start": 1730382048438472802 + "duration": 49333, + "start": 1733391672235532884 }], [ { @@ -296,11 +300,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -311,7 +315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -322,13 +326,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -340,9 +344,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -350,12 +355,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47958, - "start": 1730382048438603511 + "duration": 49500, + "start": 1733391672235663425 }], [ { @@ -368,11 +373,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -383,7 +388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -394,13 +399,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -410,9 +415,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -420,10 +426,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 39708, - "start": 1730382048438757594 + "duration": 43667, + "start": 1733391672235825425 }], [ { @@ -436,11 +442,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -451,7 +457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -462,13 +468,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -479,9 +485,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -489,10 +496,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 35834, - "start": 1730382048438876177 + "duration": 39083, + "start": 1733391672235962509 }], [ { @@ -505,11 +512,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -520,7 +527,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -531,13 +538,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -548,9 +555,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -558,10 +566,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 36334, - "start": 1730382048438990927 + "duration": 38000, + "start": 1733391672236079550 }], [ { @@ -574,11 +582,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -589,7 +597,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -600,13 +608,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_atr_mix_pass", @@ -618,9 +626,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -628,12 +637,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 12, "test.source.start": 4 }, - "duration": 61250, - "start": 1730382048439103427 + "duration": 65042, + "start": 1733391672236195925 }], [ { @@ -646,11 +655,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -661,7 +670,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -672,13 +681,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -689,9 +698,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -699,10 +709,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 34750, - "start": 1730382048439236052 + "duration": 36417, + "start": 1733391672236335175 }], [ { @@ -715,11 +725,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -730,7 +740,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -741,13 +751,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -758,9 +768,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test.type": "test", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test" }, "metrics": { @@ -768,10 +779,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 31750, - "start": 1730382048439344844 + "duration": 34292, + "start": 1733391672236440300 }], [ { @@ -784,11 +795,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -799,7 +810,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -810,13 +821,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_pass", @@ -824,7 +835,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "1034076129593796617", + "test_session_id": "4934949241130311940", "type": "test_session_end" }, "metrics": { @@ -832,10 +843,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 22246416, - "start": 1730382048418576886 + "duration": 18473958, + "start": 1733391672219401092 }, { "name": "test_visibility.module", @@ -847,11 +858,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -862,7 +873,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -873,12 +884,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_pass", @@ -888,8 +899,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", "type": "test_module_end" }, "metrics": { @@ -897,8 +908,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20740792, - "start": 1730382048418834802 + "duration": 16912292, + "start": 1733391672219754050 }, { "name": "test_visibility.suite", @@ -910,11 +921,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -925,7 +936,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -936,12 +947,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -950,9 +961,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5900551553974211565", - "test_session_id": "1034076129593796617", - "test_suite_id": "9882456734014148400", + "test_module_id": "4694807067196704107", + "test_session_id": "4934949241130311940", + "test_suite_id": "12765447051136548180", "type": "test_suite_end" }, "metrics": { @@ -960,8 +971,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20619625, - "start": 1730382048418861552 + "duration": 16784000, + "start": 1733391672219792550 }, { "name": "test_visibility.module", @@ -973,11 +984,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -988,7 +999,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -999,12 +1010,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_atr_mix_pass", @@ -1014,8 +1025,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", "type": "test_module_end" }, "metrics": { @@ -1023,8 +1034,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1095542, - "start": 1730382048439632802 + "duration": 1061416, + "start": 1733391672236724134 }, { "name": "test_visibility.suite", @@ -1036,11 +1047,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1051,7 +1062,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1062,12 +1073,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1076,9 +1087,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test_suite_end" }, "metrics": { @@ -1086,8 +1097,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 979458, - "start": 1730382048439657011 + "duration": 962125, + "start": 1733391672236752509 }], [ { @@ -1100,11 +1111,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1115,7 +1126,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1126,13 +1137,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1143,9 +1154,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1153,12 +1165,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 2, "test.source.start": 1 }, - "duration": 49834, - "start": 1730382048439690052 + "duration": 46167, + "start": 1733391672236775342 }], [ { @@ -1171,11 +1183,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1186,7 +1198,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1197,13 +1209,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1213,9 +1225,10 @@ "test.name": "m2_s1_t2", "test.status": "fail", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1223,10 +1236,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 33417, - "start": 1730382048439815802 + "duration": 49542, + "start": 1733391672236893217 }], [ { @@ -1239,11 +1252,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1254,7 +1267,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1265,13 +1278,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1282,9 +1295,10 @@ "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1292,10 +1306,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 35334, - "start": 1730382048439930552 + "duration": 37167, + "start": 1733391672237029342 }], [ { @@ -1308,11 +1322,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1323,7 +1337,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1334,13 +1348,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1351,9 +1365,10 @@ "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1361,10 +1376,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 34500, - "start": 1730382048440040969 + "duration": 35500, + "start": 1733391672237139800 }], [ { @@ -1377,11 +1392,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1392,7 +1407,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1403,13 +1418,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1420,9 +1435,10 @@ "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1430,10 +1446,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 40000, - "start": 1730382048440154261 + "duration": 34750, + "start": 1733391672237249134 }], [ { @@ -1446,11 +1462,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1461,7 +1477,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1472,13 +1488,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1489,9 +1505,10 @@ "test.name": "m2_s1_t2", "test.status": "skip", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1499,10 +1516,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 34209, - "start": 1730382048440271302 + "duration": 33792, + "start": 1733391672237353592 }], [ { @@ -1515,11 +1532,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1530,7 +1547,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1541,13 +1558,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_atr_mix_pass", "test.framework": "dd_manual_test_fw", @@ -1558,9 +1575,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1568,10 +1586,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737 + "process_id": 32278 }, - "duration": 32667, - "start": 1730382048440386052 + "duration": 36625, + "start": 1733391672237463300 }], [ { @@ -1584,11 +1602,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_atr_mix_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "672388e000000000", + "_dd.p.tid": "6751753800000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1599,7 +1617,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-385/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_atr_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1610,13 +1628,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.17.0.dev5+g019ce1525.d20241031", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "eee2f2381a844611ae9892f21a8a6b66", + "runtime-id": "6c43005477024899a88405c43babfcab", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_atr_mix_pass", @@ -1628,9 +1646,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7420958806206448377", - "test_session_id": "1034076129593796617", - "test_suite_id": "7457937023855141098", + "test.type": "test", + "test_module_id": "9424913037094513516", + "test_session_id": "4934949241130311940", + "test_suite_id": "17852177343308732286", "type": "test" }, "metrics": { @@ -1638,10 +1657,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 80737, + "process_id": 32278, "test.source.end": 12, "test.source.start": 4 }, - "duration": 53959, - "start": 1730382048440494552 + "duration": 50833, + "start": 1733391672237575842 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json index 2ed126360da..bcadbc0684b 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_all_pass.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -53,9 +53,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -63,12 +64,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234661209, - "start": 1732017031398084177 + "duration": 1234653417, + "start": 1733391727188543340 }], [ { @@ -85,7 +86,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -96,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -107,11 +108,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -126,9 +127,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -136,12 +138,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 84417, - "start": 1732017032647964594 + "duration": 97916, + "start": 1733391728438344799 }], [ { @@ -158,7 +160,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -169,7 +171,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -180,11 +182,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -199,9 +201,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -209,12 +212,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 50292, - "start": 1732017032648149219 + "duration": 52958, + "start": 1733391728438541299 }], [ { @@ -231,7 +234,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -242,7 +245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -253,11 +256,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -272,9 +275,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -282,12 +286,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45583, - "start": 1732017032648277594 + "duration": 48959, + "start": 1733391728438675715 }], [ { @@ -304,7 +308,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -315,7 +319,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -326,11 +330,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -345,9 +349,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -355,12 +360,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42500, - "start": 1732017032648394761 + "duration": 47666, + "start": 1733391728438800299 }], [ { @@ -377,7 +382,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -388,7 +393,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -399,11 +404,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -418,9 +423,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -428,12 +434,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42708, - "start": 1732017032648507386 + "duration": 47958, + "start": 1733391728438925549 }], [ { @@ -450,7 +456,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -461,7 +467,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -472,11 +478,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -491,9 +497,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -501,12 +508,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42541, - "start": 1732017032648618636 + "duration": 43833, + "start": 1733391728439044174 }], [ { @@ -523,7 +530,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -534,7 +541,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -545,11 +552,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -564,9 +571,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -574,12 +582,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43209, - "start": 1732017032648732052 + "duration": 43833, + "start": 1733391728439156924 }], [ { @@ -596,7 +604,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -607,7 +615,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -618,11 +626,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -637,9 +645,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -647,12 +656,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 56875, - "start": 1732017032648847719 + "duration": 43542, + "start": 1733391728439268965 }], [ { @@ -669,7 +678,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -680,7 +689,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -691,11 +700,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -710,9 +719,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -720,12 +730,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 66792, - "start": 1732017032648980219 + "duration": 55542, + "start": 1733391728439381340 }], [ { @@ -742,7 +752,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -753,7 +763,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -764,11 +774,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -783,9 +793,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -793,12 +804,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 64542, - "start": 1732017032649171969 + "duration": 44250, + "start": 1733391728439513715 }], [ { @@ -815,7 +826,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,11 +848,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -854,9 +865,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -864,10 +876,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 6543266750, - "start": 1732017026106176886 + "duration": 6543251541, + "start": 1733391721896425424 }], [ { @@ -884,7 +896,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -895,7 +907,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -906,11 +918,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -924,9 +936,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -934,10 +947,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 38417, - "start": 1732017032649531219 + "duration": 34000, + "start": 1733391728439749965 }], [ { @@ -954,7 +967,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -965,7 +978,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -976,11 +989,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -994,9 +1007,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1004,10 +1018,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 37292, - "start": 1732017032649645802 + "duration": 33625, + "start": 1733391728439851632 }], [ { @@ -1024,7 +1038,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1035,7 +1049,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1046,11 +1060,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1064,9 +1078,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1074,10 +1089,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 48833, - "start": 1732017032649827761 + "duration": 34291, + "start": 1733391728439954799 }], [ { @@ -1094,7 +1109,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1105,7 +1120,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1116,11 +1131,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1134,9 +1149,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1144,10 +1160,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 37208, - "start": 1732017032649957136 + "duration": 34250, + "start": 1733391728440056340 }], [ { @@ -1164,7 +1180,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1175,7 +1191,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1186,11 +1202,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1204,9 +1220,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1214,10 +1231,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 33208, - "start": 1732017032650066219 + "duration": 31834, + "start": 1733391728440157590 }], [ { @@ -1234,7 +1251,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1262,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,11 +1273,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1274,9 +1291,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1284,12 +1302,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 12, "test.source.start": 4 }, - "duration": 115459, - "start": 1732017032650352052 + "duration": 67417, + "start": 1733391728440263090 }], [ { @@ -1306,7 +1324,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1335,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,11 +1346,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1345,9 +1363,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1355,10 +1374,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 43042, - "start": 1732017032650542802 + "duration": 46000, + "start": 1733391728440388799 }], [ { @@ -1375,7 +1394,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1386,7 +1405,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1397,11 +1416,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1414,9 +1433,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test.type": "test", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test" }, "metrics": { @@ -1424,10 +1444,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, "duration": 41291, - "start": 1732017032650651386 + "start": 1733391728440540174 }], [ { @@ -1444,7 +1464,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1455,7 +1475,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1466,11 +1486,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1481,7 +1501,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "10359560244968602395", + "test_session_id": "11372754292406319631", "type": "test_session_end" }, "metrics": { @@ -1489,10 +1509,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 20544333, - "start": 1732017032632107886 + "duration": 20031875, + "start": 1733391728422516090 }, { "name": "test_visibility.module", @@ -1508,7 +1528,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1519,7 +1539,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1530,7 +1550,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1545,8 +1565,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", "type": "test_module_end" }, "metrics": { @@ -1554,8 +1574,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18323375, - "start": 1732017032632584552 + "duration": 17735667, + "start": 1733391728423034632 }, { "name": "test_visibility.suite", @@ -1571,7 +1591,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1582,7 +1602,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1593,7 +1613,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1607,9 +1627,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "5725003850113608623", - "test_session_id": "10359560244968602395", - "test_suite_id": "5168108260619620972", + "test_module_id": "10204182735731537153", + "test_session_id": "11372754292406319631", + "test_suite_id": "2083142980793588842", "type": "test_suite_end" }, "metrics": { @@ -1617,8 +1637,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18177084, - "start": 1732017032632614302 + "duration": 17608625, + "start": 1733391728423070215 }, { "name": "test_visibility.module", @@ -1634,7 +1654,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1645,7 +1665,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1656,7 +1676,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1671,8 +1691,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", "type": "test_module_end" }, "metrics": { @@ -1680,8 +1700,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1590458, - "start": 1732017032650960761 + "duration": 1615458, + "start": 1733391728440822424 }, { "name": "test_visibility.suite", @@ -1697,7 +1717,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1708,7 +1728,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1719,7 +1739,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1733,9 +1753,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test_suite_end" }, "metrics": { @@ -1743,8 +1763,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 885792, - "start": 1732017032650986594 + "duration": 899583, + "start": 1733391728440847507 }, { "name": "test_visibility.suite", @@ -1760,7 +1780,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1771,7 +1791,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1782,7 +1802,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1796,9 +1816,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test_suite_end" }, "metrics": { @@ -1806,8 +1826,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 545458, - "start": 1732017032651922719 + "duration": 551375, + "start": 1733391728441798549 }], [ { @@ -1824,7 +1844,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1835,7 +1855,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1846,11 +1866,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1862,9 +1882,10 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -1872,10 +1893,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 39125, - "start": 1732017032651006219 + "duration": 38542, + "start": 1733391728440868715 }], [ { @@ -1892,7 +1913,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1903,7 +1924,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1914,11 +1935,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1930,9 +1951,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -1940,10 +1962,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 36333, - "start": 1732017032651104219 + "duration": 38667, + "start": 1733391728440965507 }], [ { @@ -1960,7 +1982,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1971,7 +1993,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1982,11 +2004,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1998,9 +2020,10 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2008,10 +2031,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 35375, - "start": 1732017032651196552 + "duration": 37458, + "start": 1733391728441060257 }], [ { @@ -2028,7 +2051,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2039,7 +2062,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2050,11 +2073,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2066,9 +2089,10 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2076,10 +2100,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 35000, - "start": 1732017032651288386 + "duration": 36833, + "start": 1733391728441156049 }], [ { @@ -2096,7 +2120,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2107,7 +2131,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2118,11 +2142,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2134,9 +2158,10 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2144,10 +2169,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 36292, - "start": 1732017032651380927 + "duration": 35791, + "start": 1733391728441248924 }], [ { @@ -2164,7 +2189,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2175,7 +2200,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2186,11 +2211,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2202,9 +2227,10 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2212,10 +2238,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 36583, - "start": 1732017032651485636 + "duration": 37375, + "start": 1733391728441340257 }], [ { @@ -2232,7 +2258,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2243,7 +2269,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2254,11 +2280,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2270,9 +2296,10 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2280,10 +2307,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 35375, - "start": 1732017032651576386 + "duration": 36708, + "start": 1733391728441444049 }], [ { @@ -2300,7 +2327,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2311,7 +2338,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2322,11 +2349,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2338,9 +2365,10 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2348,10 +2376,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 34542, - "start": 1732017032651667552 + "duration": 38666, + "start": 1733391728441534424 }], [ { @@ -2368,7 +2396,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2379,7 +2407,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2390,11 +2418,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2406,9 +2434,10 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "16321082474709595335", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "16936266246841821198", "type": "test" }, "metrics": { @@ -2416,10 +2445,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 33708, - "start": 1732017032651755719 + "duration": 39125, + "start": 1733391728441629632 }], [ { @@ -2436,7 +2465,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2447,7 +2476,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2458,11 +2487,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2475,9 +2504,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test" }, "metrics": { @@ -2485,12 +2515,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52333, - "start": 1732017032651943344 + "duration": 48834, + "start": 1733391728441819215 }], [ { @@ -2507,7 +2537,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2518,7 +2548,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2529,11 +2559,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2547,9 +2577,10 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test" }, "metrics": { @@ -2557,10 +2588,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 301000042125, - "start": 1732016731652053344 + "duration": 301000041250, + "start": 1733391427441925674 }], [ { @@ -2577,7 +2608,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2588,7 +2619,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2599,11 +2630,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2618,9 +2649,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test" }, "metrics": { @@ -2628,12 +2660,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724, + "process_id": 32557, "test.source.end": 12, "test.source.start": 4 }, - "duration": 54833, - "start": 1732017032652158344 + "duration": 56500, + "start": 1733391728442030257 }], [ { @@ -2650,7 +2682,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2661,7 +2693,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2672,11 +2704,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2688,9 +2720,10 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test" }, "metrics": { @@ -2698,10 +2731,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 34542, - "start": 1732017032652269802 + "duration": 37000, + "start": 1733391728442144882 }], [ { @@ -2718,7 +2751,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8800000000", + "_dd.p.tid": "6751757000000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2729,7 +2762,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_all_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2740,11 +2773,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "0728e603bc8846889415595c15019eb7", + "runtime-id": "85d23ceb30e2493e8c42047bf9d2e100", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2756,9 +2789,10 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "2177262413998606555", - "test_session_id": "10359560244968602395", - "test_suite_id": "9627260334977856774", + "test.type": "test", + "test_module_id": "14844464421068762406", + "test_session_id": "11372754292406319631", + "test_suite_id": "7930343477805738115", "type": "test" }, "metrics": { @@ -2766,8 +2800,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38724 + "process_id": 32557 }, - "duration": 34958, - "start": 1732017032652360261 + "duration": 37541, + "start": 1733391728442238299 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json index d928f7b2340..db01cee9ccf 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -53,9 +53,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test.type": "test", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test" }, "metrics": { @@ -63,12 +64,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755, + "process_id": 32495, "test.source.end": 2, "test.source.start": 1 }, - "duration": 94917, - "start": 1732017042085932542 + "duration": 81875, + "start": 1733391714548202334 }], [ { @@ -85,7 +86,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -96,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -107,11 +108,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -124,9 +125,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test.type": "test", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test" }, "metrics": { @@ -134,10 +136,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 95916, - "start": 1732017042106447751 + "duration": 66625, + "start": 1733391714563523250 }], [ { @@ -154,7 +156,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -165,7 +167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -176,11 +178,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -194,9 +196,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test.type": "test", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test" }, "metrics": { @@ -204,12 +207,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755, + "process_id": 32495, "test.source.end": 12, "test.source.start": 4 }, - "duration": 77750, - "start": 1732017042106673417 + "duration": 71208, + "start": 1733391714563681417 }], [ { @@ -226,7 +229,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -237,7 +240,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -248,11 +251,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -265,9 +268,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test.type": "test", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test" }, "metrics": { @@ -275,10 +279,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 46209, - "start": 1732017042106826542 + "duration": 36083, + "start": 1733391714563825542 }], [ { @@ -295,7 +299,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -306,7 +310,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -317,11 +321,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -334,9 +338,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test.type": "test", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test" }, "metrics": { @@ -344,10 +349,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 37125, - "start": 1732017042106954959 + "duration": 35125, + "start": 1733391714563932792 }], [ { @@ -364,7 +369,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -375,7 +380,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -386,11 +391,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -402,7 +407,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "13536905200439174622", + "test_session_id": "15705414272000062156", "type": "test_session_end" }, "metrics": { @@ -410,10 +415,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 23710042, - "start": 1732017042085392042 + "duration": 18241542, + "start": 1733391714547670500 }, { "name": "test_visibility.module", @@ -429,7 +434,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -440,7 +445,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -451,7 +456,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -466,8 +471,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", "type": "test_module_end" }, "metrics": { @@ -475,8 +480,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 21324541, - "start": 1732017042085874126 + "duration": 16004833, + "start": 1733391714548140542 }, { "name": "test_visibility.suite", @@ -492,7 +497,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -503,7 +508,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -514,7 +519,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -528,9 +533,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "6680100430092765332", - "test_session_id": "13536905200439174622", - "test_suite_id": "921439586437549873", + "test_module_id": "7932912067234031810", + "test_session_id": "15705414272000062156", + "test_suite_id": "845846750843749324", "type": "test_suite_end" }, "metrics": { @@ -538,8 +543,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 21202958, - "start": 1732017042085901959 + "duration": 15893417, + "start": 1733391714548171417 }, { "name": "test_visibility.module", @@ -555,7 +560,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -566,7 +571,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -577,7 +582,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -592,8 +597,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", "type": "test_module_end" }, "metrics": { @@ -601,8 +606,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1728375, - "start": 1732017042107254584 + "duration": 1614125, + "start": 1733391714564197709 }, { "name": "test_visibility.suite", @@ -618,7 +623,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -629,7 +634,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -640,7 +645,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -654,9 +659,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test_suite_end" }, "metrics": { @@ -664,8 +669,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 932500, - "start": 1732017042107278876 + "duration": 900042, + "start": 1733391714564224375 }, { "name": "test_visibility.suite", @@ -681,7 +686,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -692,7 +697,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -703,7 +708,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -717,9 +722,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test_suite_end" }, "metrics": { @@ -727,8 +732,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 636416, - "start": 1732017042108264001 + "duration": 559708, + "start": 1733391714565176084 }], [ { @@ -745,7 +750,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -756,7 +761,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -767,11 +772,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -784,9 +789,10 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -794,10 +800,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, "duration": 34542, - "start": 1732017042107299417 + "start": 1733391714564244375 }], [ { @@ -814,7 +820,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -825,7 +831,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -836,11 +842,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -853,9 +859,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -863,10 +870,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 32166, - "start": 1732017042107404001 + "duration": 33542, + "start": 1733391714564346250 }], [ { @@ -883,7 +890,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -894,7 +901,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -905,11 +912,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -922,9 +929,10 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -932,10 +940,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 31708, - "start": 1732017042107500376 + "duration": 33250, + "start": 1733391714564456125 }], [ { @@ -952,7 +960,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -963,7 +971,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -974,11 +982,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -991,9 +999,10 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1001,10 +1010,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 32375, - "start": 1732017042107595626 + "duration": 32250, + "start": 1733391714564552959 }], [ { @@ -1021,7 +1030,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1032,7 +1041,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1043,11 +1052,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1060,9 +1069,10 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1070,10 +1080,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 34958, - "start": 1732017042107707209 + "duration": 30417, + "start": 1733391714564645042 }], [ { @@ -1090,7 +1100,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1101,7 +1111,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1112,11 +1122,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1129,9 +1139,10 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1139,10 +1150,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 30750, - "start": 1732017042107806834 + "duration": 31583, + "start": 1733391714564735834 }], [ { @@ -1159,7 +1170,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1170,7 +1181,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1181,11 +1192,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1198,9 +1209,10 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1208,10 +1220,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 31041, - "start": 1732017042107900501 + "duration": 31666, + "start": 1733391714564827459 }], [ { @@ -1228,7 +1240,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1239,7 +1251,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1250,11 +1262,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1267,9 +1279,10 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1277,10 +1290,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 31167, - "start": 1732017042107992792 + "duration": 31750, + "start": 1733391714564918667 }], [ { @@ -1297,7 +1310,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1308,7 +1321,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1319,11 +1332,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1336,9 +1349,10 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "436066589762063296", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "3960011827465066479", "type": "test" }, "metrics": { @@ -1346,10 +1360,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 30625, - "start": 1732017042108095459 + "duration": 32291, + "start": 1733391714565010709 }], [ { @@ -1366,7 +1380,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1377,7 +1391,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1388,11 +1402,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1405,9 +1419,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test" }, "metrics": { @@ -1415,12 +1430,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755, + "process_id": 32495, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44375, - "start": 1732017042108283959 + "duration": 44250, + "start": 1733391714565197084 }], [ { @@ -1437,7 +1452,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1448,7 +1463,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1459,11 +1474,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1476,9 +1491,10 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test" }, "metrics": { @@ -1486,10 +1502,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 33083, - "start": 1732017042108396584 + "duration": 32666, + "start": 1733391714565305209 }], [ { @@ -1506,7 +1522,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1517,7 +1533,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1528,11 +1544,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1547,9 +1563,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test" }, "metrics": { @@ -1557,12 +1574,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755, + "process_id": 32495, "test.source.end": 12, "test.source.start": 4 }, - "duration": 52750, - "start": 1732017042108494626 + "duration": 59625, + "start": 1733391714565399292 }], [ { @@ -1579,7 +1596,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1590,7 +1607,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1601,11 +1618,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1618,9 +1635,10 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test" }, "metrics": { @@ -1628,10 +1646,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 35375, - "start": 1732017042108617667 + "duration": 32292, + "start": 1733391714565524000 }], [ { @@ -1648,7 +1666,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9200000000", + "_dd.p.tid": "6751756200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1659,7 +1677,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1670,11 +1688,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "e1319046da864b348691a08852b812aa", + "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1686,9 +1704,10 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "9158184750463906951", - "test_session_id": "13536905200439174622", - "test_suite_id": "12430754391017424803", + "test.type": "test", + "test_module_id": "17675353669520667242", + "test_session_id": "15705414272000062156", + "test_suite_id": "599512189588521228", "type": "test" }, "metrics": { @@ -1696,8 +1715,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38755 + "process_id": 32495 }, - "duration": 31833, - "start": 1732017042108784959 + "duration": 30792, + "start": 1733391714565625125 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json index f6c8b2cbd50..ca18c3ed700 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_fail.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -53,9 +53,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -63,12 +64,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234653625, - "start": 1732017045301859670 + "duration": 1234689125, + "start": 1733391698939135550 }], [ { @@ -85,7 +86,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -96,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -107,11 +108,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -126,9 +127,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -136,12 +138,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 96708, - "start": 1732017046552997878 + "duration": 101375, + "start": 1733391700191411341 }], [ { @@ -158,7 +160,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -169,7 +171,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -180,11 +182,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -199,9 +201,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -209,12 +212,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52875, - "start": 1732017046553204003 + "duration": 63333, + "start": 1733391700191635383 }], [ { @@ -231,7 +234,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -242,7 +245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -253,11 +256,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -272,9 +275,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -282,12 +286,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47125, - "start": 1732017046553335503 + "duration": 60833, + "start": 1733391700191798300 }], [ { @@ -304,7 +308,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -315,7 +319,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -326,11 +330,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -345,9 +349,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -355,12 +360,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48583, - "start": 1732017046553474503 + "duration": 62459, + "start": 1733391700191975966 }], [ { @@ -377,7 +382,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -388,7 +393,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -399,11 +404,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -418,9 +423,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -428,12 +434,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42750, - "start": 1732017046553594545 + "duration": 84208, + "start": 1733391700192166008 }], [ { @@ -450,7 +456,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -461,7 +467,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -472,11 +478,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -491,9 +497,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -501,12 +508,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48375, - "start": 1732017046553735753 + "duration": 64541, + "start": 1733391700192364550 }], [ { @@ -523,7 +530,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -534,7 +541,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -545,11 +552,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -564,9 +571,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -574,12 +582,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43667, - "start": 1732017046553858211 + "duration": 60209, + "start": 1733391700192527341 }], [ { @@ -596,7 +604,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -607,7 +615,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -618,11 +626,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -637,9 +645,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -647,12 +656,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 42958, - "start": 1732017046553970920 + "duration": 56583, + "start": 1733391700192680758 }], [ { @@ -669,7 +678,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -680,7 +689,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -691,11 +700,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -710,9 +719,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -720,12 +730,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45750, - "start": 1732017046554098211 + "duration": 57250, + "start": 1733391700192829175 }], [ { @@ -742,7 +752,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -753,7 +763,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -764,11 +774,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -783,9 +793,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -793,12 +804,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 41917, - "start": 1732017046554212711 + "duration": 59667, + "start": 1733391700192996008 }], [ { @@ -815,7 +826,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,11 +848,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -854,9 +865,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -864,10 +876,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 6543248625, - "start": 1732017040011126045 + "duration": 6543263417, + "start": 1733391693649958091 }], [ { @@ -884,7 +896,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -895,7 +907,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -906,11 +918,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -924,9 +936,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -934,10 +947,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 32792, - "start": 1732017046554443461 + "duration": 44334, + "start": 1733391700193317591 }], [ { @@ -954,7 +967,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -965,7 +978,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -976,11 +989,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -994,9 +1007,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1004,10 +1018,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 80333, - "start": 1732017046554544878 + "duration": 44458, + "start": 1733391700193450383 }], [ { @@ -1024,7 +1038,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1035,7 +1049,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1046,11 +1060,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1064,9 +1078,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1074,10 +1089,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 35167, - "start": 1732017046554721336 + "duration": 43416, + "start": 1733391700193592800 }], [ { @@ -1094,7 +1109,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1105,7 +1120,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1116,11 +1131,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1134,9 +1149,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1144,10 +1160,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 32625, - "start": 1732017046554827295 + "duration": 43375, + "start": 1733391700193723383 }], [ { @@ -1164,7 +1180,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1175,7 +1191,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1186,11 +1202,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1204,9 +1220,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1214,10 +1231,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 32334, - "start": 1732017046554926836 + "duration": 42583, + "start": 1733391700193856508 }], [ { @@ -1234,7 +1251,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1262,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,11 +1273,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1274,9 +1291,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1284,12 +1302,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 12, "test.source.start": 4 }, - "duration": 86583, - "start": 1732017046555034003 + "duration": 86458, + "start": 1733391700194006258 }], [ { @@ -1306,7 +1324,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1335,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,11 +1346,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1345,9 +1363,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1355,10 +1374,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 39250, - "start": 1732017046555184920 + "duration": 48917, + "start": 1733391700194170841 }], [ { @@ -1375,7 +1394,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1386,7 +1405,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1397,11 +1416,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1414,9 +1433,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test.type": "test", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test" }, "metrics": { @@ -1424,10 +1444,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 36583, - "start": 1732017046555281670 + "duration": 53750, + "start": 1733391700194350175 }], [ { @@ -1444,7 +1464,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1455,7 +1475,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1466,11 +1486,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1481,7 +1501,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "8540679216798029288", + "test_session_id": "13253808241663960106", "type": "test_session_end" }, "metrics": { @@ -1489,10 +1509,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 21474500, - "start": 1732017046535832128 + "duration": 24292041, + "start": 1733391700172699300 }, { "name": "test_visibility.module", @@ -1508,7 +1528,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1519,7 +1539,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1530,7 +1550,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1545,8 +1565,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "fail", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", "type": "test_module_end" }, "metrics": { @@ -1554,8 +1574,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 19144958, - "start": 1732017046536355503 + "duration": 21167500, + "start": 1733391700173475925 }, { "name": "test_visibility.suite", @@ -1571,7 +1591,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1582,7 +1602,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1593,7 +1613,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1607,9 +1627,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15225714558851568795", - "test_session_id": "8540679216798029288", - "test_suite_id": "2519961191575781039", + "test_module_id": "7960304805596441933", + "test_session_id": "13253808241663960106", + "test_suite_id": "14585279263808320623", "type": "test_suite_end" }, "metrics": { @@ -1617,8 +1637,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 19024250, - "start": 1732017046536387836 + "duration": 20912000, + "start": 1733391700173615883 }, { "name": "test_visibility.module", @@ -1634,7 +1654,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1645,7 +1665,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1656,7 +1676,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1671,8 +1691,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", "type": "test_module_end" }, "metrics": { @@ -1680,8 +1700,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1650917, - "start": 1732017046555550253 + "duration": 2127500, + "start": 1733391700194714550 }, { "name": "test_visibility.suite", @@ -1697,7 +1717,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1708,7 +1728,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1719,7 +1739,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1733,9 +1753,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test_suite_end" }, "metrics": { @@ -1743,8 +1763,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 914292, - "start": 1732017046555574086 + "duration": 1191666, + "start": 1733391700194744800 }, { "name": "test_visibility.suite", @@ -1760,7 +1780,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1771,7 +1791,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1782,7 +1802,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1796,9 +1816,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test_suite_end" }, "metrics": { @@ -1806,8 +1826,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 579250, - "start": 1732017046556536836 + "duration": 736875, + "start": 1733391700196006050 }], [ { @@ -1824,7 +1844,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1835,7 +1855,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1846,11 +1866,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1862,9 +1882,10 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -1872,10 +1893,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 37875, - "start": 1732017046555593045 + "duration": 50917, + "start": 1733391700194771716 }], [ { @@ -1892,7 +1913,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1903,7 +1924,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1914,11 +1935,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1930,9 +1951,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -1940,10 +1962,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 34916, - "start": 1732017046555688295 + "duration": 61541, + "start": 1733391700194899800 }], [ { @@ -1960,7 +1982,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1971,7 +1993,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1982,11 +2004,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1998,9 +2020,10 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2008,10 +2031,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 37625, - "start": 1732017046555779711 + "duration": 49333, + "start": 1733391700195038800 }], [ { @@ -2028,7 +2051,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2039,7 +2062,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2050,11 +2073,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2066,9 +2089,10 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2076,10 +2100,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 34792, - "start": 1732017046555873378 + "duration": 46125, + "start": 1733391700195161258 }], [ { @@ -2096,7 +2120,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2107,7 +2131,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2118,11 +2142,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2134,9 +2158,10 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2144,10 +2169,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 36041, - "start": 1732017046555966420 + "duration": 47666, + "start": 1733391700195288175 }], [ { @@ -2164,7 +2189,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2175,7 +2200,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2186,11 +2211,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2202,9 +2227,10 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2212,10 +2238,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 37125, - "start": 1732017046556075211 + "duration": 45791, + "start": 1733391700195409050 }], [ { @@ -2232,7 +2258,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2243,7 +2269,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2254,11 +2280,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2270,9 +2296,10 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2280,10 +2307,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 37667, - "start": 1732017046556187586 + "duration": 46417, + "start": 1733391700195526591 }], [ { @@ -2300,7 +2327,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2311,7 +2338,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2322,11 +2349,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2338,9 +2365,10 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2348,10 +2376,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 34625, - "start": 1732017046556280336 + "duration": 46000, + "start": 1733391700195646341 }], [ { @@ -2368,7 +2396,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2379,7 +2407,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2390,11 +2418,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2406,9 +2434,10 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "14859399231932022925", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "12467821484386904457", "type": "test" }, "metrics": { @@ -2416,10 +2445,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 33917, - "start": 1732017046556369461 + "duration": 53416, + "start": 1733391700195763300 }], [ { @@ -2436,7 +2465,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2447,7 +2476,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2458,11 +2487,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2475,9 +2504,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test" }, "metrics": { @@ -2485,12 +2515,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52667, - "start": 1732017046556557211 + "duration": 63291, + "start": 1733391700196033175 }], [ { @@ -2507,7 +2537,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2518,7 +2548,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2529,11 +2559,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2547,9 +2577,10 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test" }, "metrics": { @@ -2557,10 +2588,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 301000040708, - "start": 1732016745556675003 + "duration": 301000053166, + "start": 1733391399196177925 }], [ { @@ -2577,7 +2608,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2588,7 +2619,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2599,11 +2630,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2618,9 +2649,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test" }, "metrics": { @@ -2628,12 +2660,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786, + "process_id": 32433, "test.source.end": 12, "test.source.start": 4 }, - "duration": 55625, - "start": 1732017046556776586 + "duration": 74916, + "start": 1733391700196316675 }], [ { @@ -2650,7 +2682,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2661,7 +2693,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2672,11 +2704,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2688,9 +2720,10 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test" }, "metrics": { @@ -2698,10 +2731,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 35625, - "start": 1732017046556891378 + "duration": 49125, + "start": 1733391700196469800 }], [ { @@ -2718,7 +2751,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b9600000000", + "_dd.p.tid": "6751755400000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2729,7 +2762,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_fail0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_fail0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2740,11 +2773,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "abe1a8e5930e46b98093e2feda810990", + "runtime-id": "dd555b6adc9d40fb9f89bc7df347ba0d", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2756,9 +2789,10 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "16469156105143312938", - "test_session_id": "8540679216798029288", - "test_suite_id": "12744719041350872463", + "test.type": "test", + "test_module_id": "5861453389647612492", + "test_session_id": "13253808241663960106", + "test_suite_id": "16305413671566770107", "type": "test" }, "metrics": { @@ -2766,8 +2800,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38786 + "process_id": 32433 }, - "duration": 35917, - "start": 1732017046556981961 + "duration": 47084, + "start": 1733391700196599466 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json index 429b78d489d..90d0e7b6674 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_mix_pass.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,11 +35,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -53,9 +53,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -63,12 +64,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 1234651750, - "start": 1732017027028168925 + "duration": 1234652792, + "start": 1733391675454915636 }], [ { @@ -85,7 +86,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -96,7 +97,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -107,11 +108,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -126,9 +127,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -136,12 +138,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 96209, - "start": 1732017028281164633 + "duration": 79542, + "start": 1733391676704279719 }], [ { @@ -158,7 +160,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -169,7 +171,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -180,11 +182,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -199,9 +201,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -209,12 +212,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 87959, - "start": 1732017028281384008 + "duration": 52083, + "start": 1733391676704453886 }], [ { @@ -231,7 +234,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -242,7 +245,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -253,11 +256,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -272,9 +275,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -282,12 +286,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52834, - "start": 1732017028281572008 + "duration": 48208, + "start": 1733391676704585636 }], [ { @@ -304,7 +308,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -315,7 +319,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -326,11 +330,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -345,9 +349,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -355,12 +360,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46916, - "start": 1732017028281702342 + "duration": 47917, + "start": 1733391676704706011 }], [ { @@ -377,7 +382,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -388,7 +393,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -399,11 +404,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -418,9 +423,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -428,12 +434,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 66458, - "start": 1732017028281827717 + "duration": 48417, + "start": 1733391676704829594 }], [ { @@ -450,7 +456,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -461,7 +467,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -472,11 +478,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -491,9 +497,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -501,12 +508,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48750, - "start": 1732017028281971133 + "duration": 44292, + "start": 1733391676704963761 }], [ { @@ -523,7 +530,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -534,7 +541,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -545,11 +552,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -564,9 +571,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -574,12 +582,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45625, - "start": 1732017028282096758 + "duration": 45166, + "start": 1733391676705080178 }], [ { @@ -596,7 +604,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -607,7 +615,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -618,11 +626,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -637,9 +645,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -647,12 +656,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47792, - "start": 1732017028282217758 + "duration": 43375, + "start": 1733391676705192136 }], [ { @@ -669,7 +678,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -680,7 +689,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -691,11 +700,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -710,9 +719,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -720,12 +730,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 45416, - "start": 1732017028282340467 + "duration": 42458, + "start": 1733391676705303886 }], [ { @@ -742,7 +752,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -753,7 +763,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -764,11 +774,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -783,9 +793,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -793,12 +804,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 46375, - "start": 1732017028282461050 + "duration": 44167, + "start": 1733391676705412094 }], [ { @@ -815,7 +826,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -826,7 +837,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -837,11 +848,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -854,9 +865,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -864,10 +876,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 6543256458, - "start": 1732017021739387925 + "duration": 6543253125, + "start": 1733391670162323011 }], [ { @@ -884,7 +896,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -895,7 +907,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -906,11 +918,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -924,9 +936,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -934,10 +947,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 35584, - "start": 1732017028282723133 + "duration": 34416, + "start": 1733391676705647803 }], [ { @@ -954,7 +967,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -965,7 +978,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -976,11 +989,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -994,9 +1007,10 @@ "test.name": "m1_s1_t2", "test.status": "fail", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1004,10 +1018,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 119500, - "start": 1732017028282831967 + "duration": 32792, + "start": 1733391676705748011 }], [ { @@ -1024,7 +1038,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1035,7 +1049,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1046,11 +1060,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1064,9 +1078,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1074,10 +1089,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 36458, - "start": 1732017028283035217 + "duration": 32416, + "start": 1733391676705852303 }], [ { @@ -1094,7 +1109,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1105,7 +1120,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1116,11 +1131,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1134,9 +1149,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1144,10 +1160,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 34709, - "start": 1732017028283147133 + "duration": 34125, + "start": 1733391676705957761 }], [ { @@ -1164,7 +1180,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1175,7 +1191,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1186,11 +1202,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1204,9 +1220,10 @@ "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1214,10 +1231,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 34667, - "start": 1732017028283255925 + "duration": 32209, + "start": 1733391676706058469 }], [ { @@ -1234,7 +1251,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1245,7 +1262,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1256,11 +1273,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1274,9 +1291,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1284,12 +1302,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 12, "test.source.start": 4 }, - "duration": 76083, - "start": 1732017028283367175 + "duration": 64542, + "start": 1733391676706158219 }], [ { @@ -1306,7 +1324,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1317,7 +1335,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1328,11 +1346,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1345,9 +1363,10 @@ "test.parameters": "{\"param1\": \"value1\"}", "test.status": "skip", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1355,10 +1374,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 39667, - "start": 1732017028283505883 + "duration": 38083, + "start": 1733391676706280928 }], [ { @@ -1375,7 +1394,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1386,7 +1405,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1397,11 +1416,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1414,9 +1433,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test.type": "test", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test" }, "metrics": { @@ -1424,10 +1444,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 37125, - "start": 1732017028283606383 + "duration": 38042, + "start": 1733391676706419261 }], [ { @@ -1444,7 +1464,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1455,7 +1475,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1466,11 +1486,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1481,7 +1501,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "13096961797776614253", + "test_session_id": "11793065164916846017", "type": "test_session_end" }, "metrics": { @@ -1489,10 +1509,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 23619459, - "start": 1732017028262062008 + "duration": 19461125, + "start": 1733391676688918261 }, { "name": "test_visibility.module", @@ -1508,7 +1528,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1519,7 +1539,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1530,7 +1550,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1545,8 +1565,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", "type": "test_module_end" }, "metrics": { @@ -1554,8 +1574,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 21200959, - "start": 1732017028262645133 + "duration": 17237209, + "start": 1733391676689410719 }, { "name": "test_visibility.suite", @@ -1571,7 +1591,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1582,7 +1602,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1593,7 +1613,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1607,9 +1627,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "15860744644384930388", - "test_session_id": "13096961797776614253", - "test_suite_id": "7512659030108198446", + "test_module_id": "6318635904052741877", + "test_session_id": "11793065164916846017", + "test_suite_id": "15602870130272609799", "type": "test_suite_end" }, "metrics": { @@ -1617,8 +1637,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 21060625, - "start": 1732017028262685550 + "duration": 17112167, + "start": 1733391676689443344 }, { "name": "test_visibility.module", @@ -1634,7 +1654,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1645,7 +1665,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1656,7 +1676,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1671,8 +1691,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", "type": "test_module_end" }, "metrics": { @@ -1680,8 +1700,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1657291, - "start": 1732017028283910592 + "duration": 1583625, + "start": 1733391676706698303 }, { "name": "test_visibility.suite", @@ -1697,7 +1717,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1708,7 +1728,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1719,7 +1739,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1733,9 +1753,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test_suite_end" }, "metrics": { @@ -1743,8 +1763,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 902875, - "start": 1732017028283934508 + "duration": 869167, + "start": 1733391676706726594 }, { "name": "test_visibility.suite", @@ -1760,7 +1780,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1771,7 +1791,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1782,7 +1802,7 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", @@ -1796,9 +1816,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test_suite_end" }, "metrics": { @@ -1806,8 +1826,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 583333, - "start": 1732017028284896842 + "duration": 556541, + "start": 1733391676707645678 }], [ { @@ -1824,7 +1844,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1835,7 +1855,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1846,11 +1866,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1862,9 +1882,10 @@ "test.name": "m2_s1_t1", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -1872,10 +1893,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 39375, - "start": 1732017028283955592 + "duration": 38583, + "start": 1733391676706747053 }], [ { @@ -1892,7 +1913,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1903,7 +1924,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1914,11 +1935,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1930,9 +1951,10 @@ "test.name": "m2_s1_t2", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -1940,10 +1962,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 36334, - "start": 1732017028284053383 + "duration": 36959, + "start": 1733391676706841719 }], [ { @@ -1960,7 +1982,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1971,7 +1993,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1982,11 +2004,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -1998,9 +2020,10 @@ "test.name": "m2_s1_t3", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2008,10 +2031,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 38791, - "start": 1732017028284149717 + "duration": 37958, + "start": 1733391676706947011 }], [ { @@ -2028,7 +2051,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2039,7 +2062,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2050,11 +2073,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2066,9 +2089,10 @@ "test.name": "m2_s1_t4", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2076,10 +2100,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 35166, - "start": 1732017028284245967 + "duration": 35708, + "start": 1733391676707041678 }], [ { @@ -2096,7 +2120,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2107,7 +2131,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2118,11 +2142,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2134,9 +2158,10 @@ "test.name": "m2_s1_t5", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2144,10 +2169,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 40209, - "start": 1732017028284340883 + "duration": 36708, + "start": 1733391676707132386 }], [ { @@ -2164,7 +2189,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2175,7 +2200,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2186,11 +2211,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2202,9 +2227,10 @@ "test.name": "m2_s1_t6", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2212,10 +2238,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 35500, - "start": 1732017028284441008 + "duration": 34792, + "start": 1733391676707222261 }], [ { @@ -2232,7 +2258,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2243,7 +2269,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2254,11 +2280,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2270,9 +2296,10 @@ "test.name": "m2_s1_t7", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2280,10 +2307,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 37792, - "start": 1732017028284536050 + "duration": 34375, + "start": 1733391676707309803 }], [ { @@ -2300,7 +2327,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2311,7 +2338,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2322,11 +2349,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2338,9 +2365,10 @@ "test.name": "m2_s1_t8", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2348,10 +2376,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 34750, - "start": 1732017028284629467 + "duration": 35125, + "start": 1733391676707398969 }], [ { @@ -2368,7 +2396,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2379,7 +2407,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2390,11 +2418,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2406,9 +2434,10 @@ "test.name": "m2_s1_t9", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "10499803900482435818", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "16074680904973015831", "type": "test" }, "metrics": { @@ -2416,10 +2445,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 36833, - "start": 1732017028284720675 + "duration": 34666, + "start": 1733391676707486303 }], [ { @@ -2436,7 +2465,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2447,7 +2476,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2458,11 +2487,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2475,9 +2504,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test" }, "metrics": { @@ -2485,12 +2515,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52042, - "start": 1732017028284917425 + "duration": 48292, + "start": 1733391676707666136 }], [ { @@ -2507,7 +2537,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2518,7 +2548,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2529,11 +2559,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2547,9 +2577,10 @@ "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test" }, "metrics": { @@ -2557,10 +2588,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 301000042833, - "start": 1732016727285032342 + "duration": 301000041958, + "start": 1733391375707769011 }], [ { @@ -2577,7 +2608,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2588,7 +2619,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2599,11 +2630,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2618,9 +2649,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test" }, "metrics": { @@ -2628,12 +2660,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693, + "process_id": 32309, "test.source.end": 12, "test.source.start": 4 }, - "duration": 60667, - "start": 1732017028285140300 + "duration": 61667, + "start": 1733391676707871011 }], [ { @@ -2650,7 +2682,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2661,7 +2693,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2672,11 +2704,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2688,9 +2720,10 @@ "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test" }, "metrics": { @@ -2698,10 +2731,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 39000, - "start": 1732017028285264008 + "duration": 43959, + "start": 1733391676707992969 }], [ { @@ -2718,7 +2751,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "673c7b8400000000", + "_dd.p.tid": "6751753c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2729,7 +2762,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-6/test_manual_api_fake_efd_mix_pass0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_mix_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2740,11 +2773,11 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev22+g4d6c975d3.d20241119", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "feec2a03be4b4409b4d460473f0578c5", + "runtime-id": "d0081f95abc846b88909338dd5cc19fa", "runtime.name": "CPython", "runtime.version": "3.9.19", "span.kind": "test", @@ -2756,9 +2789,10 @@ "test.name": "m2_s2_t5", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "7734405348027363975", - "test_session_id": "13096961797776614253", - "test_suite_id": "116850239999929103", + "test.type": "test", + "test_module_id": "6667426030585195188", + "test_session_id": "11793065164916846017", + "test_suite_id": "5084028275255797648", "type": "test" }, "metrics": { @@ -2766,8 +2800,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 38693 + "process_id": 32309 }, - "duration": 40291, - "start": 1732017028285362717 + "duration": 36542, + "start": 1733391676708091719 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json index b94cc7a2a02..9c0cad08e5c 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_fail.json @@ -9,23 +9,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -36,9 +36,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "16269247376577165045", - "test_session_id": "10225274619407140469", - "test_suite_id": "15436573267184258232", + "test.type": "test", + "test_module_id": "11304147311645547221", + "test_session_id": "15553571577477592129", + "test_suite_id": "8772450378135620219", "type": "test" }, "metrics": { @@ -46,12 +47,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203, + "process_id": 32185, "test.source.end": 2, "test.source.start": 1 }, - "duration": 172875, - "start": 1726064460764653086 + "duration": 201292, + "start": 1733391658527634044 }], [ { @@ -64,23 +65,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -90,9 +91,10 @@ "test.name": "test_2", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "16269247376577165045", - "test_session_id": "10225274619407140469", - "test_suite_id": "15436573267184258232", + "test.type": "test", + "test_module_id": "11304147311645547221", + "test_session_id": "15553571577477592129", + "test_suite_id": "8772450378135620219", "type": "test" }, "metrics": { @@ -100,10 +102,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203 + "process_id": 32185 }, - "duration": 120250, - "start": 1726064460782645295 + "duration": 127750, + "start": 1733391658548015086 }], [ { @@ -116,23 +118,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_all_fail", @@ -144,9 +146,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "16269247376577165045", - "test_session_id": "10225274619407140469", - "test_suite_id": "15436573267184258232", + "test.type": "test", + "test_module_id": "11304147311645547221", + "test_session_id": "15553571577477592129", + "test_suite_id": "8772450378135620219", "type": "test" }, "metrics": { @@ -154,12 +157,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203, + "process_id": 32185, "test.source.end": 12, "test.source.start": 4 }, - "duration": 94375, - "start": 1726064460782849253 + "duration": 100083, + "start": 1733391658548222711 }], [ { @@ -172,20 +175,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_fail", @@ -193,7 +196,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "10225274619407140469", + "test_session_id": "15553571577477592129", "type": "test_session_end" }, "metrics": { @@ -201,10 +204,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203 + "process_id": 32185 }, - "duration": 19172792, - "start": 1726064460764551878 + "duration": 21580208, + "start": 1733391658527497836 }, { "name": "test_visibility.module", @@ -216,19 +219,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_fail", @@ -238,8 +241,8 @@ "test.module": "module_1", "test.module_path": "", "test.status": "fail", - "test_module_id": "16269247376577165045", - "test_session_id": "10225274619407140469", + "test_module_id": "11304147311645547221", + "test_session_id": "15553571577477592129", "type": "test_module_end" }, "metrics": { @@ -247,8 +250,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18517541, - "start": 1726064460764600545 + "duration": 20903417, + "start": 1733391658527574919 }, { "name": "test_visibility.suite", @@ -260,19 +263,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -281,9 +284,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "16269247376577165045", - "test_session_id": "10225274619407140469", - "test_suite_id": "15436573267184258232", + "test_module_id": "11304147311645547221", + "test_session_id": "15553571577477592129", + "test_suite_id": "8772450378135620219", "type": "test_suite_end" }, "metrics": { @@ -291,8 +294,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18425250, - "start": 1726064460764624211 + "duration": 20805792, + "start": 1733391658527602294 }, { "name": "test_visibility.module", @@ -304,19 +307,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_fail", @@ -326,8 +329,8 @@ "test.module": "module_2", "test.module_path": "", "test.status": "fail", - "test_module_id": "4992766612005539652", - "test_session_id": "10225274619407140469", + "test_module_id": "14739150656002093412", + "test_session_id": "15553571577477592129", "type": "test_module_end" }, "metrics": { @@ -335,8 +338,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 505833, - "start": 1726064460783158295 + "duration": 484959, + "start": 1733391658548519127 }, { "name": "test_visibility.suite", @@ -348,19 +351,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -369,9 +372,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "suite_2", - "test_module_id": "4992766612005539652", - "test_session_id": "10225274619407140469", - "test_suite_id": "8663789166215047252", + "test_module_id": "14739150656002093412", + "test_session_id": "15553571577477592129", + "test_suite_id": "15553985716416792388", "type": "test_suite_end" }, "metrics": { @@ -379,8 +382,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 421625, - "start": 1726064460783184670 + "duration": 405292, + "start": 1733391658548545294 }], [ { @@ -393,23 +396,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -420,9 +423,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "suite_2", - "test_module_id": "4992766612005539652", - "test_session_id": "10225274619407140469", - "test_suite_id": "8663789166215047252", + "test.type": "test", + "test_module_id": "14739150656002093412", + "test_session_id": "15553571577477592129", + "test_suite_id": "15553985716416792388", "type": "test" }, "metrics": { @@ -430,12 +434,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203, + "process_id": 32185, "test.source.end": 2, "test.source.start": 1 }, - "duration": 71417, - "start": 1726064460783212503 + "duration": 68959, + "start": 1733391658548566877 }], [ { @@ -448,23 +452,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_fail", "test.framework": "dd_manual_test_fw", @@ -474,9 +478,10 @@ "test.name": "test_2", "test.status": "fail", "test.suite": "suite_2", - "test_module_id": "4992766612005539652", - "test_session_id": "10225274619407140469", - "test_suite_id": "8663789166215047252", + "test.type": "test", + "test_module_id": "14739150656002093412", + "test_session_id": "15553571577477592129", + "test_suite_id": "15553985716416792388", "type": "test" }, "metrics": { @@ -484,10 +489,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203 + "process_id": 32185 }, - "duration": 49875, - "start": 1726064460783354128 + "duration": 51500, + "start": 1733391658548686169 }], [ { @@ -500,23 +505,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74c00000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751752a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_fail0/fake_runner_all_fail.py\", line 12, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "94122a67f41a4269ac6811ae80a9a94c", + "runtime-id": "10605f61cd394a34930b487248eb8d93", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_all_fail", @@ -528,9 +533,10 @@ "test.source.file": "my_file_1.py", "test.status": "fail", "test.suite": "suite_2", - "test_module_id": "4992766612005539652", - "test_session_id": "10225274619407140469", - "test_suite_id": "8663789166215047252", + "test.type": "test", + "test_module_id": "14739150656002093412", + "test_session_id": "15553571577477592129", + "test_suite_id": "15553985716416792388", "type": "test" }, "metrics": { @@ -538,10 +544,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96203, + "process_id": 32185, "test.source.end": 12, "test.source.start": 4 }, - "duration": 85083, - "start": 1726064460783452628 + "duration": 77166, + "start": 1733391658548786211 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json index ddf2efb62b7..89fbf5d972a 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_suite_level.json @@ -9,20 +9,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -37,9 +37,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "8146721796243144334", - "test_session_id": "3124265812194288315", - "test_suite_id": "3579446507413690401", + "test.type": "test", + "test_module_id": "18232151536133109336", + "test_session_id": "4514488857097750323", + "test_suite_id": "6106393041952600343", "type": "test" }, "metrics": { @@ -47,12 +48,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610, + "process_id": 32464, "test.source.end": 2, "test.source.start": 1 }, - "duration": 87209, - "start": 1727949193286896886 + "duration": 110500, + "start": 1733391710055956888 }], [ { @@ -65,20 +66,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -92,9 +93,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "8146721796243144334", - "test_session_id": "3124265812194288315", - "test_suite_id": "3579446507413690401", + "test.type": "test", + "test_module_id": "18232151536133109336", + "test_session_id": "4514488857097750323", + "test_suite_id": "6106393041952600343", "type": "test" }, "metrics": { @@ -102,10 +104,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610 + "process_id": 32464 }, - "duration": 60541, - "start": 1727949193302721845 + "duration": 73334, + "start": 1733391710071311804 }], [ { @@ -118,20 +120,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_all_itr_skip_suite_level", @@ -147,9 +149,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "8146721796243144334", - "test_session_id": "3124265812194288315", - "test_suite_id": "3579446507413690401", + "test.type": "test", + "test_module_id": "18232151536133109336", + "test_session_id": "4514488857097750323", + "test_suite_id": "6106393041952600343", "type": "test" }, "metrics": { @@ -157,12 +160,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610, + "process_id": 32464, "test.source.end": 12, "test.source.start": 4 }, - "duration": 99875, - "start": 1727949193302855553 + "duration": 77334, + "start": 1733391710071455929 }], [ { @@ -175,21 +178,21 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip_suite_level", @@ -202,7 +205,7 @@ "test.itr.unskippable": "false", "test.skipped_by_itr": "false", "test.status": "skip", - "test_session_id": "3124265812194288315", + "test_session_id": "4514488857097750323", "type": "test_session_end" }, "metrics": { @@ -210,11 +213,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610, + "process_id": 32464, "test.itr.tests_skipping.count": 2 }, - "duration": 16892916, - "start": 1727949193286788720 + "duration": 16637167, + "start": 1733391710055763179 }, { "name": "test_visibility.module", @@ -226,20 +229,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip_suite_level", @@ -254,8 +257,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "skip", - "test_module_id": "8146721796243144334", - "test_session_id": "3124265812194288315", + "test_module_id": "18232151536133109336", + "test_session_id": "4514488857097750323", "type": "test_module_end" }, "metrics": { @@ -264,8 +267,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 16312625, - "start": 1727949193286834053 + "duration": 15873958, + "start": 1733391710055843013 }, { "name": "test_visibility.suite", @@ -277,19 +280,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -303,9 +306,9 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "8146721796243144334", - "test_session_id": "3124265812194288315", - "test_suite_id": "3579446507413690401", + "test_module_id": "18232151536133109336", + "test_session_id": "4514488857097750323", + "test_suite_id": "6106393041952600343", "type": "test_suite_end" }, "metrics": { @@ -314,8 +317,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 16205292, - "start": 1727949193286857053 + "duration": 15766500, + "start": 1733391710055871846 }, { "name": "test_visibility.module", @@ -327,20 +330,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip_suite_level", @@ -355,8 +358,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "skip", - "test_module_id": "8115688887202783463", - "test_session_id": "3124265812194288315", + "test_module_id": "17648680694713508140", + "test_session_id": "4514488857097750323", "type": "test_module_end" }, "metrics": { @@ -365,8 +368,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 424167, - "start": 1727949193303188011 + "duration": 546708, + "start": 1733391710071761221 }, { "name": "test_visibility.suite", @@ -378,19 +381,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -404,9 +407,9 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "8115688887202783463", - "test_session_id": "3124265812194288315", - "test_suite_id": "4144404284095535186", + "test_module_id": "17648680694713508140", + "test_session_id": "4514488857097750323", + "test_suite_id": "11914312247862756146", "type": "test_suite_end" }, "metrics": { @@ -415,8 +418,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 340292, - "start": 1727949193303211261 + "duration": 396958, + "start": 1733391710071788096 }], [ { @@ -429,20 +432,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -457,9 +460,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "8115688887202783463", - "test_session_id": "3124265812194288315", - "test_suite_id": "4144404284095535186", + "test.type": "test", + "test_module_id": "17648680694713508140", + "test_session_id": "4514488857097750323", + "test_suite_id": "11914312247862756146", "type": "test" }, "metrics": { @@ -467,12 +471,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610, + "process_id": 32464, "test.source.end": 2, "test.source.start": 1 }, - "duration": 52125, - "start": 1727949193303230636 + "duration": 56166, + "start": 1733391710071809888 }], [ { @@ -485,20 +489,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip_suite_level", "test.framework": "dd_manual_test_fw", @@ -512,9 +516,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "8115688887202783463", - "test_session_id": "3124265812194288315", - "test_suite_id": "4144404284095535186", + "test.type": "test", + "test_module_id": "17648680694713508140", + "test_session_id": "4514488857097750323", + "test_suite_id": "11914312247862756146", "type": "test" }, "metrics": { @@ -522,10 +527,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610 + "process_id": 32464 }, - "duration": 37416, - "start": 1727949193303336970 + "duration": 55750, + "start": 1733391710071915263 }], [ { @@ -538,20 +543,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_suite_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe698900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-113/ddtrace_subprocess_dir", + "_dd.p.tid": "6751755e00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_suite_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "2fc66620ce044c73ae45a8c09630d89e", + "runtime-id": "a8d5a902c2bd4d898a75241f014c0426", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_all_itr_skip_suite_level", @@ -567,9 +572,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "8115688887202783463", - "test_session_id": "3124265812194288315", - "test_suite_id": "4144404284095535186", + "test.type": "test", + "test_module_id": "17648680694713508140", + "test_session_id": "4514488857097750323", + "test_suite_id": "11914312247862756146", "type": "test" }, "metrics": { @@ -577,10 +583,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 610, + "process_id": 32464, "test.source.end": 12, "test.source.start": 4 }, - "duration": 52000, - "start": 1727949193303424511 + "duration": 57667, + "start": 1733391710072020346 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json index db965dd97cb..c6eff61f352 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_itr_skip_test_level.json @@ -9,20 +9,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -37,9 +37,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "462204304223044491", - "test_session_id": "6660381732268366109", - "test_suite_id": "18179087129848574199", + "test.type": "test", + "test_module_id": "12451241884981509136", + "test_session_id": "2845096446065343180", + "test_suite_id": "1207949669061626814", "type": "test" }, "metrics": { @@ -47,12 +48,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045, + "process_id": 32588, "test.source.end": 2, "test.source.start": 1 }, - "duration": 158084, - "start": 1727949461566940135 + "duration": 177666, + "start": 1733391732960833801 }], [ { @@ -65,20 +66,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -92,9 +93,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "462204304223044491", - "test_session_id": "6660381732268366109", - "test_suite_id": "18179087129848574199", + "test.type": "test", + "test_module_id": "12451241884981509136", + "test_session_id": "2845096446065343180", + "test_suite_id": "1207949669061626814", "type": "test" }, "metrics": { @@ -102,10 +104,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045 + "process_id": 32588 }, - "duration": 74333, - "start": 1727949461579881177 + "duration": 108250, + "start": 1733391732981370468 }], [ { @@ -118,20 +120,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_all_itr_skip", @@ -147,9 +149,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "462204304223044491", - "test_session_id": "6660381732268366109", - "test_suite_id": "18179087129848574199", + "test.type": "test", + "test_module_id": "12451241884981509136", + "test_session_id": "2845096446065343180", + "test_suite_id": "1207949669061626814", "type": "test" }, "metrics": { @@ -157,12 +160,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045, + "process_id": 32588, "test.source.end": 12, "test.source.start": 4 }, - "duration": 71375, - "start": 1727949461580036719 + "duration": 84625, + "start": 1733391732981559426 }], [ { @@ -175,21 +178,21 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip", @@ -202,7 +205,7 @@ "test.itr.unskippable": "false", "test.skipped_by_itr": "false", "test.status": "skip", - "test_session_id": "6660381732268366109", + "test_session_id": "2845096446065343180", "type": "test_session_end" }, "metrics": { @@ -210,11 +213,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045, + "process_id": 32588, "test.itr.tests_skipping.count": 6 }, - "duration": 14010250, - "start": 1727949461566827302 + "duration": 21703834, + "start": 1733391732960678592 }, { "name": "test_visibility.module", @@ -226,20 +229,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip", @@ -254,8 +257,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "skip", - "test_module_id": "462204304223044491", - "test_session_id": "6660381732268366109", + "test_module_id": "12451241884981509136", + "test_session_id": "2845096446065343180", "type": "test_module_end" }, "metrics": { @@ -264,8 +267,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 3 }, - "duration": 13412709, - "start": 1727949461566882885 + "duration": 21051626, + "start": 1733391732960768342 }, { "name": "test_visibility.suite", @@ -277,19 +280,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -302,9 +305,9 @@ "test.skipped_by_itr": "false", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "462204304223044491", - "test_session_id": "6660381732268366109", - "test_suite_id": "18179087129848574199", + "test_module_id": "12451241884981509136", + "test_session_id": "2845096446065343180", + "test_suite_id": "1207949669061626814", "type": "test_suite_end" }, "metrics": { @@ -313,8 +316,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 3 }, - "duration": 13300958, - "start": 1727949461566910344 + "duration": 20940001, + "start": 1733391732960799967 }, { "name": "test_visibility.module", @@ -326,20 +329,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_itr_skip", @@ -354,8 +357,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "skip", - "test_module_id": "2933987231402311937", - "test_session_id": "6660381732268366109", + "test_module_id": "14896662768064729037", + "test_session_id": "2845096446065343180", "type": "test_module_end" }, "metrics": { @@ -364,8 +367,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 3 }, - "duration": 428916, - "start": 1727949461580335969 + "duration": 438958, + "start": 1733391732981863176 }, { "name": "test_visibility.suite", @@ -377,19 +380,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -402,9 +405,9 @@ "test.skipped_by_itr": "false", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "2933987231402311937", - "test_session_id": "6660381732268366109", - "test_suite_id": "16340892961687085043", + "test_module_id": "14896662768064729037", + "test_session_id": "2845096446065343180", + "test_suite_id": "5928216281214013985", "type": "test_suite_end" }, "metrics": { @@ -413,8 +416,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 3 }, - "duration": 343042, - "start": 1727949461580358760 + "duration": 348084, + "start": 1733391732981891134 }], [ { @@ -427,20 +430,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -455,9 +458,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "2933987231402311937", - "test_session_id": "6660381732268366109", - "test_suite_id": "16340892961687085043", + "test.type": "test", + "test_module_id": "14896662768064729037", + "test_session_id": "2845096446065343180", + "test_suite_id": "5928216281214013985", "type": "test" }, "metrics": { @@ -465,12 +469,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045, + "process_id": 32588, "test.source.end": 2, "test.source.start": 1 }, - "duration": 53750, - "start": 1727949461580378677 + "duration": 59917, + "start": 1733391732981913926 }], [ { @@ -483,20 +487,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_itr_skip", "test.framework": "dd_manual_test_fw", @@ -510,9 +514,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "2933987231402311937", - "test_session_id": "6660381732268366109", - "test_suite_id": "16340892961687085043", + "test.type": "test", + "test_module_id": "14896662768064729037", + "test_session_id": "2845096446065343180", + "test_suite_id": "5928216281214013985", "type": "test" }, "metrics": { @@ -520,10 +525,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045 + "process_id": 32588 }, - "duration": 37375, - "start": 1727949461580484510 + "duration": 42125, + "start": 1733391732982024968 }], [ { @@ -536,20 +541,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_itr_skip_test_level0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe6a9500000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-120/ddtrace_subprocess_dir", + "_dd.p.tid": "6751757400000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_itr_skip_test_level0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "3fbf18bc565243f083b1c154f0a21c80", + "runtime-id": "5f8def5cf2184d7b985f696300fcee92", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_all_itr_skip", @@ -565,9 +570,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "2933987231402311937", - "test_session_id": "6660381732268366109", - "test_suite_id": "16340892961687085043", + "test.type": "test", + "test_module_id": "14896662768064729037", + "test_session_id": "2845096446065343180", + "test_suite_id": "5928216281214013985", "type": "test" }, "metrics": { @@ -575,10 +581,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 3045, + "process_id": 32588, "test.source.end": 12, "test.source.start": 4 }, - "duration": 58167, - "start": 1727949461580571135 + "duration": 58250, + "start": 1733391732982114468 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json index f2229ef33b4..97def4a74ee 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_pass.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -52,9 +52,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "13733519753593649642", - "test_session_id": "12861571989375419490", - "test_suite_id": "9922751308260105935", + "test.type": "test", + "test_module_id": "246542248116353062", + "test_session_id": "4339259438825257500", + "test_suite_id": "13099766750729197090", "type": "test" }, "metrics": { @@ -62,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110, + "process_id": 32619, "test.source.end": 2, "test.source.start": 1 }, - "duration": 78833, - "start": 1726064444451573760 + "duration": 159833, + "start": 1733391737429657345 }], [ { @@ -80,11 +81,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -95,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -106,13 +107,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -122,9 +123,10 @@ "test.name": "test_2", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "13733519753593649642", - "test_session_id": "12861571989375419490", - "test_suite_id": "9922751308260105935", + "test.type": "test", + "test_module_id": "246542248116353062", + "test_session_id": "4339259438825257500", + "test_suite_id": "13099766750729197090", "type": "test" }, "metrics": { @@ -132,10 +134,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110 + "process_id": 32619 }, - "duration": 60292, - "start": 1726064444468127801 + "duration": 63875, + "start": 1733391737446842261 }], [ { @@ -148,11 +150,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -163,7 +165,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -174,13 +176,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_all_pass", @@ -192,9 +194,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "13733519753593649642", - "test_session_id": "12861571989375419490", - "test_suite_id": "9922751308260105935", + "test.type": "test", + "test_module_id": "246542248116353062", + "test_session_id": "4339259438825257500", + "test_suite_id": "13099766750729197090", "type": "test" }, "metrics": { @@ -202,12 +205,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110, + "process_id": 32619, "test.source.end": 12, "test.source.start": 4 }, - "duration": 70583, - "start": 1726064444468300760 + "duration": 73375, + "start": 1733391737446989053 }], [ { @@ -220,11 +223,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -235,7 +238,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -246,13 +249,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_pass", @@ -260,7 +263,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "12861571989375419490", + "test_session_id": "4339259438825257500", "type": "test_session_end" }, "metrics": { @@ -268,10 +271,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110 + "process_id": 32619 }, - "duration": 17675458, - "start": 1726064444451461093 + "duration": 18347083, + "start": 1733391737429498053 }, { "name": "test_visibility.module", @@ -283,11 +286,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -298,7 +301,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -309,12 +312,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_pass", @@ -324,8 +327,8 @@ "test.module": "module_1", "test.module_path": "", "test.status": "pass", - "test_module_id": "13733519753593649642", - "test_session_id": "12861571989375419490", + "test_module_id": "246542248116353062", + "test_session_id": "4339259438825257500", "type": "test_module_end" }, "metrics": { @@ -333,8 +336,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 17036250, - "start": 1726064444451524218 + "duration": 17669000, + "start": 1733391737429585636 }, { "name": "test_visibility.suite", @@ -346,11 +349,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -361,7 +364,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -372,12 +375,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -386,9 +389,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "13733519753593649642", - "test_session_id": "12861571989375419490", - "test_suite_id": "9922751308260105935", + "test_module_id": "246542248116353062", + "test_session_id": "4339259438825257500", + "test_suite_id": "13099766750729197090", "type": "test_suite_end" }, "metrics": { @@ -396,8 +399,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 16923209, - "start": 1726064444451547301 + "duration": 17547666, + "start": 1733391737429621970 }, { "name": "test_visibility.module", @@ -409,11 +412,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -424,7 +427,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -435,12 +438,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_pass", @@ -450,8 +453,8 @@ "test.module": "module_2", "test.module_path": "", "test.status": "pass", - "test_module_id": "15025138991962623711", - "test_session_id": "12861571989375419490", + "test_module_id": "8323877848174581582", + "test_session_id": "4339259438825257500", "type": "test_module_end" }, "metrics": { @@ -459,8 +462,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 430375, - "start": 1726064444468616343 + "duration": 457000, + "start": 1733391737447309053 }, { "name": "test_visibility.suite", @@ -472,11 +475,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -487,7 +490,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -498,12 +501,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -512,9 +515,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "15025138991962623711", - "test_session_id": "12861571989375419490", - "test_suite_id": "8184921218016920577", + "test_module_id": "8323877848174581582", + "test_session_id": "4339259438825257500", + "test_suite_id": "1575148813078467344", "type": "test_suite_end" }, "metrics": { @@ -522,8 +525,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 343542, - "start": 1726064444468639718 + "duration": 363084, + "start": 1733391737447335386 }], [ { @@ -536,11 +539,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -551,7 +554,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -562,13 +565,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -579,9 +582,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "15025138991962623711", - "test_session_id": "12861571989375419490", - "test_suite_id": "8184921218016920577", + "test.type": "test", + "test_module_id": "8323877848174581582", + "test_session_id": "4339259438825257500", + "test_suite_id": "1575148813078467344", "type": "test" }, "metrics": { @@ -589,12 +593,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110, + "process_id": 32619, "test.source.end": 2, "test.source.start": 1 }, - "duration": 43625, - "start": 1726064444468659093 + "duration": 48709, + "start": 1733391737447357511 }], [ { @@ -607,11 +611,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -622,7 +626,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -633,13 +637,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_pass", "test.framework": "dd_manual_test_fw", @@ -649,9 +653,10 @@ "test.name": "test_2", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "15025138991962623711", - "test_session_id": "12861571989375419490", - "test_suite_id": "8184921218016920577", + "test.type": "test", + "test_module_id": "8323877848174581582", + "test_session_id": "4339259438825257500", + "test_suite_id": "1575148813078467344", "type": "test" }, "metrics": { @@ -659,10 +664,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110 + "process_id": 32619 }, - "duration": 31792, - "start": 1726064444468765176 + "duration": 33708, + "start": 1733391737447483678 }], [ { @@ -675,11 +680,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_pass0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73c00000000", + "_dd.p.tid": "6751757900000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -690,7 +695,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_pass0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -701,13 +706,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1800bf5a07b2408cbfb668570e406ed1", + "runtime-id": "31e0b1dc579f47e4a24ccb4980fe961d", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_all_pass", @@ -719,9 +724,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "15025138991962623711", - "test_session_id": "12861571989375419490", - "test_suite_id": "8184921218016920577", + "test.type": "test", + "test_module_id": "8323877848174581582", + "test_session_id": "4339259438825257500", + "test_suite_id": "1575148813078467344", "type": "test" }, "metrics": { @@ -729,10 +735,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96110, + "process_id": 32619, "test.source.end": 12, "test.source.start": 4 }, - "duration": 47000, - "start": 1726064444468855593 + "duration": 48292, + "start": 1733391737447575178 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json index 2451fa700b5..0e6ecb0cb4f 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_all_skip.json @@ -9,20 +9,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -34,9 +34,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "5065407489991893073", - "test_session_id": "1140591219199170133", - "test_suite_id": "14940785380780726404", + "test.type": "test", + "test_module_id": "7952106378091003395", + "test_session_id": "8693376587523876752", + "test_suite_id": "16446503812325564625", "type": "test" }, "metrics": { @@ -44,12 +45,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327, + "process_id": 32247, "test.source.end": 2, "test.source.start": 1 }, - "duration": 75667, - "start": 1726064480534516387 + "duration": 81542, + "start": 1733391667552481673 }], [ { @@ -62,20 +63,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -86,9 +87,10 @@ "test.skip_reason": "This test was skipped", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "5065407489991893073", - "test_session_id": "1140591219199170133", - "test_suite_id": "14940785380780726404", + "test.type": "test", + "test_module_id": "7952106378091003395", + "test_session_id": "8693376587523876752", + "test_suite_id": "16446503812325564625", "type": "test" }, "metrics": { @@ -96,10 +98,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327 + "process_id": 32247 }, - "duration": 57792, - "start": 1726064480551057012 + "duration": 83792, + "start": 1733391667571233215 }], [ { @@ -112,20 +114,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_all_skip", @@ -138,9 +140,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "5065407489991893073", - "test_session_id": "1140591219199170133", - "test_suite_id": "14940785380780726404", + "test.type": "test", + "test_module_id": "7952106378091003395", + "test_session_id": "8693376587523876752", + "test_suite_id": "16446503812325564625", "type": "test" }, "metrics": { @@ -148,12 +151,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327, + "process_id": 32247, "test.source.end": 12, "test.source.start": 4 }, - "duration": 63750, - "start": 1726064480551201262 + "duration": 194084, + "start": 1733391667571431173 }], [ { @@ -166,20 +169,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_skip", @@ -187,7 +190,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "skip", - "test_session_id": "1140591219199170133", + "test_session_id": "8693376587523876752", "type": "test_session_end" }, "metrics": { @@ -195,10 +198,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327 + "process_id": 32247 }, - "duration": 17557209, - "start": 1726064480534400387 + "duration": 20068750, + "start": 1733391667552331673 }, { "name": "test_visibility.module", @@ -210,19 +213,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_skip", @@ -232,8 +235,8 @@ "test.module": "module_1", "test.module_path": "", "test.status": "skip", - "test_module_id": "5065407489991893073", - "test_session_id": "1140591219199170133", + "test_module_id": "7952106378091003395", + "test_session_id": "8693376587523876752", "type": "test_module_end" }, "metrics": { @@ -241,8 +244,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 16981250, - "start": 1726064480534462054 + "duration": 19433875, + "start": 1733391667552421215 }, { "name": "test_visibility.suite", @@ -254,19 +257,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -275,9 +278,9 @@ "test.module_path": "", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "5065407489991893073", - "test_session_id": "1140591219199170133", - "test_suite_id": "14940785380780726404", + "test_module_id": "7952106378091003395", + "test_session_id": "8693376587523876752", + "test_suite_id": "16446503812325564625", "type": "test_suite_end" }, "metrics": { @@ -285,8 +288,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 16882625, - "start": 1726064480534487471 + "duration": 19328000, + "start": 1733391667552448882 }, { "name": "test_visibility.module", @@ -298,19 +301,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_all_skip", @@ -320,8 +323,8 @@ "test.module": "module_2", "test.module_path": "", "test.status": "skip", - "test_module_id": "7988684972802280408", - "test_session_id": "1140591219199170133", + "test_module_id": "15676828463831728076", + "test_session_id": "8693376587523876752", "type": "test_module_end" }, "metrics": { @@ -329,8 +332,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 408583, - "start": 1726064480551485971 + "duration": 416583, + "start": 1733391667571917465 }, { "name": "test_visibility.suite", @@ -342,19 +345,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -363,9 +366,9 @@ "test.module_path": "", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "7988684972802280408", - "test_session_id": "1140591219199170133", - "test_suite_id": "17507091798133648775", + "test_module_id": "15676828463831728076", + "test_session_id": "8693376587523876752", + "test_suite_id": "18280304171416854029", "type": "test_suite_end" }, "metrics": { @@ -373,8 +376,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 325250, - "start": 1726064480551513096 + "duration": 325042, + "start": 1733391667571952298 }], [ { @@ -387,20 +390,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -412,9 +415,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "7988684972802280408", - "test_session_id": "1140591219199170133", - "test_suite_id": "17507091798133648775", + "test.type": "test", + "test_module_id": "15676828463831728076", + "test_session_id": "8693376587523876752", + "test_suite_id": "18280304171416854029", "type": "test" }, "metrics": { @@ -422,12 +426,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327, + "process_id": 32247, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48708, - "start": 1726064480551534846 + "duration": 55500, + "start": 1733391667571976382 }], [ { @@ -440,20 +444,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_all_skip", "test.framework": "dd_manual_test_fw", @@ -464,9 +468,10 @@ "test.skip_reason": "This test was skipped with mark_skip", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "7988684972802280408", - "test_session_id": "1140591219199170133", - "test_suite_id": "17507091798133648775", + "test.type": "test", + "test_module_id": "15676828463831728076", + "test_session_id": "8693376587523876752", + "test_suite_id": "18280304171416854029", "type": "test" }, "metrics": { @@ -474,10 +479,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327 + "process_id": 32247 }, - "duration": 31875, - "start": 1726064480551641346 + "duration": 35375, + "start": 1733391667572083507 }], [ { @@ -490,20 +495,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_all_skip0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a76000000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751753300000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_all_skip0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "1cea123c9dca4bc09bc3157af2b63bb3", + "runtime-id": "06bf8b092259405eabbc6a7863f3cf25", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_all_skip", @@ -516,9 +521,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "7988684972802280408", - "test_session_id": "1140591219199170133", - "test_suite_id": "17507091798133648775", + "test.type": "test", + "test_module_id": "15676828463831728076", + "test_session_id": "8693376587523876752", + "test_suite_id": "18280304171416854029", "type": "test" }, "metrics": { @@ -526,10 +532,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96327, + "process_id": 32247, "test.source.end": 12, "test.source.start": 4 }, - "duration": 49375, - "start": 1726064480551723179 + "duration": 50334, + "start": 1733391667572166798 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json index 242e7a331d9..e191f15d8e3 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail.json @@ -9,20 +9,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -33,9 +33,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -43,12 +44,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 2, "test.source.start": 1 }, - "duration": 81916, - "start": 1726064440728186258 + "duration": 130084, + "start": 1733391681166674846 }], [ { @@ -61,20 +62,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -85,9 +86,10 @@ "test.status": "skip", "test.suite": "suite_1", "test.tag1": "suite_1_test_2_id", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -95,10 +97,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079 + "process_id": 32340 }, - "duration": 100459, - "start": 1726064440747569424 + "duration": 97333, + "start": 1733391681183381555 }], [ { @@ -111,20 +113,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_mix_fail", @@ -136,9 +138,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -146,12 +149,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 12, "test.source.start": 4 }, - "duration": 73875, - "start": 1726064440747775341 + "duration": 72666, + "start": 1733391681183552805 }], [ { @@ -164,20 +167,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -190,9 +193,10 @@ "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_1_id", "test.tag2": "value_for_tag_2", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -200,11 +204,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.tag4": 4 }, - "duration": 65000, - "start": 1726064440747911341 + "duration": 67208, + "start": 1733391681183677138 }], [ { @@ -217,20 +221,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -241,9 +245,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -251,10 +256,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079 + "process_id": 32340 }, - "duration": 32500, - "start": 1726064440748032508 + "duration": 45458, + "start": 1733391681183801513 }], [ { @@ -267,23 +272,23 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir/fake_runner_mix_fail.py\", line 22, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0/fake_runner_mix_fail.py\", line 22, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -295,9 +300,10 @@ "test.status": "fail", "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_3_id", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test.type": "test", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test" }, "metrics": { @@ -305,11 +311,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.tag3": 12333333 }, - "duration": 237084, - "start": 1726064440748149674 + "duration": 185667, + "start": 1733391681183938096 }], [ { @@ -322,20 +328,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail", @@ -343,7 +349,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "fail", - "test_session_id": "13973769067339343528", + "test_session_id": "10664643294462279392", "type": "test_session_end" }, "metrics": { @@ -351,10 +357,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079 + "process_id": 32340 }, - "duration": 23037334, - "start": 1726064440727671299 + "duration": 19936667, + "start": 1733391681166111596 }, { "name": "test_visibility.module", @@ -366,19 +372,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail", @@ -388,8 +394,8 @@ "test.module": "module_1", "test.module_path": "", "test.status": "fail", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", "type": "test_module_end" }, "metrics": { @@ -397,8 +403,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20476333, - "start": 1726064440728091633 + "duration": 17682875, + "start": 1733391681166613346 }, { "name": "test_visibility.suite", @@ -410,19 +416,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -431,9 +437,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "14957432479232427106", - "test_session_id": "13973769067339343528", - "test_suite_id": "8985714909544047394", + "test_module_id": "9661031482096024159", + "test_session_id": "10664643294462279392", + "test_suite_id": "13938906197931956337", "type": "test_suite_end" }, "metrics": { @@ -441,8 +447,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 20329125, - "start": 1726064440728159841 + "duration": 17574792, + "start": 1733391681166643263 }, { "name": "test_visibility.module", @@ -454,19 +460,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail", @@ -476,8 +482,8 @@ "test.module": "module_2", "test.module_path": "", "test.status": "pass", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", "type": "test_module_end" }, "metrics": { @@ -485,8 +491,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1141709, - "start": 1726064440748612549 + "duration": 843542, + "start": 1733391681184341221 }, { "name": "test_visibility.suite", @@ -498,19 +504,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -519,9 +525,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test_suite_end" }, "metrics": { @@ -529,8 +535,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1053125, - "start": 1726064440748639008 + "duration": 746042, + "start": 1733391681184375638 }, { "name": "test_visibility.module", @@ -542,19 +548,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail", @@ -564,8 +570,8 @@ "test.module": "module_3", "test.module_path": "", "test.status": "fail", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", "type": "test_module_end" }, "metrics": { @@ -573,8 +579,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 826000, - "start": 1726064440749793008 + "duration": 743167, + "start": 1733391681185221888 }, { "name": "test_visibility.suite", @@ -586,19 +592,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -607,9 +613,9 @@ "test.module_path": "", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "10349837361753654543", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "14393273715207594430", "type": "test_suite_end" }, "metrics": { @@ -617,8 +623,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 384250, - "start": 1726064440749816883 + "duration": 306792, + "start": 1733391681185242721 }, { "name": "test_visibility.suite", @@ -630,19 +636,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -651,9 +657,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "8000045347405137637", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "10851514934449384612", "type": "test_suite_end" }, "metrics": { @@ -661,8 +667,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 309666, - "start": 1726064440750251008 + "duration": 302792, + "start": 1733391681185585971 }], [ { @@ -675,20 +681,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -699,9 +705,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -709,12 +716,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 2, "test.source.start": 1 }, - "duration": 51334, - "start": 1726064440748660924 + "duration": 50500, + "start": 1733391681184397555 }], [ { @@ -727,20 +734,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -754,9 +761,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_2_parametrized_1_id", "test.tag2": "two", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -764,13 +772,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 9, "test.source.start": 8, "test.tag3": 3 }, - "duration": 44708, - "start": 1726064440748775216 + "duration": 47292, + "start": 1733391681184506846 }], [ { @@ -783,20 +791,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -808,9 +816,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -818,12 +827,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 9, "test.source.start": 8 }, - "duration": 55041, - "start": 1726064440748872633 + "duration": 61167, + "start": 1733391681184597721 }], [ { @@ -836,20 +845,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -861,9 +870,10 @@ "test.source.file": "test_file_2.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -871,12 +881,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 9, "test.source.start": 8 }, - "duration": 73916, - "start": 1726064440749198383 + "duration": 54667, + "start": 1733391681184704471 }], [ { @@ -889,20 +899,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -914,9 +924,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -924,12 +935,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 9, "test.source.start": 8 }, - "duration": 49333, - "start": 1726064440749342841 + "duration": 40583, + "start": 1733391681184804263 }], [ { @@ -942,20 +953,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -967,9 +978,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -977,12 +989,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 9, "test.source.start": 8 }, - "duration": 43458, - "start": 1726064440749461383 + "duration": 56917, + "start": 1733391681184898013 }], [ { @@ -995,20 +1007,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_mix_fail", @@ -1022,9 +1034,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_3_id", "test.tag3": "this tag stays", - "test_module_id": "5473095056353126140", - "test_session_id": "13973769067339343528", - "test_suite_id": "2725888215571919991", + "test.type": "test", + "test_module_id": "3001821152188356003", + "test_session_id": "10664643294462279392", + "test_suite_id": "5136430613697070267", "type": "test" }, "metrics": { @@ -1032,13 +1045,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 12, "test.source.start": 4, "test.tag2": 2 }, - "duration": 60250, - "start": 1726064440749558674 + "duration": 56334, + "start": 1733391681185002346 }], [ { @@ -1051,20 +1064,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1075,9 +1088,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "10349837361753654543", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "14393273715207594430", "type": "test" }, "metrics": { @@ -1085,12 +1099,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 6, "test.source.start": 4 }, - "duration": 46875, - "start": 1726064440749898674 + "duration": 49083, + "start": 1733391681185262805 }], [ { @@ -1103,20 +1117,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1127,9 +1141,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "10349837361753654543", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "14393273715207594430", "type": "test" }, "metrics": { @@ -1137,12 +1152,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 12, "test.source.start": 9 }, - "duration": 41583, - "start": 1726064440750001841 + "duration": 41917, + "start": 1733391681185357596 }], [ { @@ -1155,20 +1170,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1179,9 +1194,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "10349837361753654543", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "14393273715207594430", "type": "test" }, "metrics": { @@ -1189,12 +1205,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 48, "test.source.start": 16 }, - "duration": 43000, - "start": 1726064440750095383 + "duration": 46042, + "start": 1733391681185443638 }], [ { @@ -1207,20 +1223,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1231,9 +1247,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "8000045347405137637", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "10851514934449384612", "type": "test" }, "metrics": { @@ -1241,12 +1258,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 6, "test.source.start": 4 }, - "duration": 45666, - "start": 1726064440750271758 + "duration": 41042, + "start": 1733391681185606638 }], [ { @@ -1259,20 +1276,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1283,9 +1300,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "8000045347405137637", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "10851514934449384612", "type": "test" }, "metrics": { @@ -1293,12 +1311,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 12, "test.source.start": 9 }, - "duration": 39667, - "start": 1726064440750368591 + "duration": 40958, + "start": 1733391681185691888 }], [ { @@ -1311,20 +1329,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a73800000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754100000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "479e8684fa124bebac1e04acb513f2d8", + "runtime-id": "6dd134230d404d5286c3d07a649e868c", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail", "test.framework": "dd_manual_test_fw", @@ -1335,9 +1353,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "1429100978976063882", - "test_session_id": "13973769067339343528", - "test_suite_id": "8000045347405137637", + "test.type": "test", + "test_module_id": "10582500816683912305", + "test_session_id": "10664643294462279392", + "test_suite_id": "10851514934449384612", "type": "test" }, "metrics": { @@ -1345,10 +1364,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96079, + "process_id": 32340, "test.source.end": 48, "test.source.start": 16 }, - "duration": 39958, - "start": 1726064440750460008 + "duration": 47542, + "start": 1733391681185776388 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json index bb1f78251c7..da5ff500168 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_suite_level.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -55,9 +55,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -65,12 +66,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 2, "test.source.start": 1 }, - "duration": 124375, - "start": 1727947161569458001 + "duration": 176292, + "start": 1733391663024604379 }], [ { @@ -83,11 +84,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -98,7 +99,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -109,13 +110,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -129,9 +130,10 @@ "test.status": "skip", "test.suite": "suite_1", "test.tag1": "suite_1_test_2_id", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -139,10 +141,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335 + "process_id": 32216 }, - "duration": 145375, - "start": 1727947161585240918 + "duration": 222167, + "start": 1733391663044830879 }], [ { @@ -155,11 +157,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -170,7 +172,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -181,13 +183,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -203,9 +205,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -213,12 +216,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 12, "test.source.start": 4 }, - "duration": 133541, - "start": 1727947161585526668 + "duration": 153375, + "start": 1733391663045158463 }], [ { @@ -231,11 +234,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -246,7 +249,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -257,13 +260,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -279,9 +282,10 @@ "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_1_id", "test.tag2": "value_for_tag_2", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -289,11 +293,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.tag4": 4 }, - "duration": 67125, - "start": 1727947161585836543 + "duration": 97958, + "start": 1733391663045475546 }], [ { @@ -306,11 +310,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -321,7 +325,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -332,13 +336,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -352,9 +356,10 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -362,10 +367,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335 + "process_id": 32216 }, - "duration": 35292, - "start": 1727947161585974334 + "duration": 47375, + "start": 1733391663045649129 }], [ { @@ -378,11 +383,11 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -393,10 +398,10 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir/fake_runner_mix_fail_itr_suite_level.py\", line 35, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0/fake_runner_mix_fail_itr_suite_level.py\", line 35, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -407,13 +412,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -428,9 +433,10 @@ "test.status": "fail", "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_3_id", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test.type": "test", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test" }, "metrics": { @@ -438,11 +444,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.tag3": 12333333 }, - "duration": 161000, - "start": 1727947161586114126 + "duration": 295667, + "start": 1733391663045789379 }], [ { @@ -455,12 +461,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -471,7 +477,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -482,13 +488,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -501,7 +507,7 @@ "test.itr.unskippable": "true", "test.skipped_by_itr": "false", "test.status": "fail", - "test_session_id": "203428879956679513", + "test_session_id": "4539691886584922642", "type": "test_session_end" }, "metrics": { @@ -509,12 +515,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.code_coverage.lines_pct": 79.79, "test.itr.tests_skipping.count": 1 }, - "duration": 21514208, - "start": 1727947161568736626 + "duration": 25941084, + "start": 1733391663023824379 }, { "name": "test_visibility.module", @@ -526,12 +532,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -542,7 +548,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -553,12 +559,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -573,8 +579,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "fail", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", "type": "test_module_end" }, "metrics": { @@ -583,8 +589,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 17097583, - "start": 1727947161569398001 + "duration": 21794750, + "start": 1733391663024534963 }, { "name": "test_visibility.suite", @@ -596,11 +602,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -611,7 +617,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -622,12 +628,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -640,9 +646,9 @@ "test.skipped_by_itr": "false", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "8886163658964650695", - "test_session_id": "203428879956679513", - "test_suite_id": "18140045957285672457", + "test_module_id": "10416634202427934507", + "test_session_id": "4539691886584922642", + "test_suite_id": "4384444981231642482", "type": "test_suite_end" }, "metrics": { @@ -651,8 +657,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 16974750, - "start": 1727947161569427668 + "duration": 21659000, + "start": 1733391663024570463 }, { "name": "test_visibility.module", @@ -664,12 +670,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -680,7 +686,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -691,12 +697,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -711,8 +717,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "pass", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", "type": "test_module_end" }, "metrics": { @@ -721,8 +727,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 1570542, - "start": 1727947161586548834 + "duration": 1061417, + "start": 1733391663046384504 }, { "name": "test_visibility.suite", @@ -734,11 +740,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -749,7 +755,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -760,12 +766,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -778,9 +784,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test_suite_end" }, "metrics": { @@ -789,8 +795,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 1440167, - "start": 1727947161586597834 + "duration": 945708, + "start": 1733391663046413046 }, { "name": "test_visibility.module", @@ -802,12 +808,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -818,7 +824,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -829,12 +835,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -849,8 +855,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "fail", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", "type": "test_module_end" }, "metrics": { @@ -859,8 +865,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 1044750, - "start": 1727947161588171084 + "duration": 1141791, + "start": 1733391663047502088 }, { "name": "test_visibility.suite", @@ -872,11 +878,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -887,7 +893,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -898,12 +904,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -916,9 +922,9 @@ "test.skipped_by_itr": "false", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "12838910888505032284", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "5020060091297303190", "type": "test_suite_end" }, "metrics": { @@ -927,8 +933,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 526916, - "start": 1727947161588192168 + "duration": 584917, + "start": 1733391663047525504 }, { "name": "test_visibility.suite", @@ -940,11 +946,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -955,7 +961,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -966,12 +972,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -984,9 +990,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "10686430323535585902", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "14887943326832864284", "type": "test_suite_end" }, "metrics": { @@ -995,8 +1001,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 362208, - "start": 1727947161588778001 + "duration": 392291, + "start": 1733391663048166463 }, { "name": "test_visibility.module", @@ -1008,12 +1014,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1024,7 +1030,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1035,12 +1041,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -1055,8 +1061,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "pass", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", "type": "test_module_end" }, "metrics": { @@ -1065,8 +1071,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 858500, - "start": 1727947161589265168 + "duration": 928208, + "start": 1733391663048698171 }, { "name": "test_visibility.suite", @@ -1078,11 +1084,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1093,7 +1099,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1104,12 +1110,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1123,9 +1129,9 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "16841724294445300103", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "18297923163300115258", "type": "test_suite_end" }, "metrics": { @@ -1134,8 +1140,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 369792, - "start": 1727947161589284001 + "duration": 411791, + "start": 1733391663048719213 }, { "name": "test_visibility.suite", @@ -1147,11 +1153,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1162,7 +1168,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1173,12 +1179,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1191,9 +1197,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_6", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "9128268971301319397", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "13895245717540955304", "type": "test_suite_end" }, "metrics": { @@ -1202,8 +1208,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 341209, - "start": 1727947161589705459 + "duration": 353125, + "start": 1733391663049183129 }], [ { @@ -1216,11 +1222,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1231,7 +1237,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1242,13 +1248,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1262,9 +1268,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1272,12 +1279,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 2, "test.source.start": 1 }, - "duration": 47417, - "start": 1727947161586617876 + "duration": 49333, + "start": 1733391663046436171 }], [ { @@ -1290,11 +1297,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1305,7 +1312,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1316,13 +1323,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1339,9 +1346,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_2_parametrized_1_id", "test.tag2": "two", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1349,13 +1357,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 9, "test.source.start": 8, "test.tag3": 3 }, - "duration": 45000, - "start": 1727947161586736876 + "duration": 46625, + "start": 1733391663046556921 }], [ { @@ -1368,11 +1376,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1383,7 +1391,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1394,13 +1402,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1415,9 +1423,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1425,12 +1434,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 9, "test.source.start": 8 }, - "duration": 55750, - "start": 1727947161586844334 + "duration": 61292, + "start": 1733391663046661921 }], [ { @@ -1443,11 +1452,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1458,7 +1467,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1469,13 +1478,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1491,9 +1500,10 @@ "test.source.file": "test_file_2.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1501,12 +1511,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 9, "test.source.start": 8 }, - "duration": 58041, - "start": 1727947161587471043 + "duration": 58750, + "start": 1733391663046780588 }], [ { @@ -1519,11 +1529,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1534,7 +1544,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1545,13 +1555,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1566,9 +1576,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1576,12 +1587,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 9, "test.source.start": 8 }, - "duration": 47250, - "start": 1727947161587598293 + "duration": 63167, + "start": 1733391663046896546 }], [ { @@ -1594,11 +1605,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1609,7 +1620,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1620,13 +1631,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1641,9 +1652,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1651,12 +1663,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 9, "test.source.start": 8 }, - "duration": 43167, - "start": 1727947161587707876 + "duration": 44042, + "start": 1733391663047025504 }], [ { @@ -1669,11 +1681,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1684,7 +1696,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1695,13 +1707,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_mix_fail_itr_suite_level", @@ -1718,9 +1730,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_3_id", "test.tag3": "this tag stays", - "test_module_id": "14169522229001369303", - "test_session_id": "203428879956679513", - "test_suite_id": "10289733921149244348", + "test.type": "test", + "test_module_id": "2976039782461944061", + "test_session_id": "4539691886584922642", + "test_suite_id": "9954554763912180380", "type": "test" }, "metrics": { @@ -1728,13 +1741,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 12, "test.source.start": 4, "test.tag2": 2 }, - "duration": 59709, - "start": 1727947161587811709 + "duration": 66292, + "start": 1733391663047136296 }], [ { @@ -1747,11 +1760,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1762,7 +1775,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1773,13 +1786,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1793,9 +1806,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "12838910888505032284", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "5020060091297303190", "type": "test" }, "metrics": { @@ -1803,12 +1817,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 6, "test.source.start": 4 }, - "duration": 129417, - "start": 1727947161588211376 + "duration": 129416, + "start": 1733391663047553213 }], [ { @@ -1821,11 +1835,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1836,7 +1850,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1847,13 +1861,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1867,9 +1881,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "12838910888505032284", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "5020060091297303190", "type": "test" }, "metrics": { @@ -1877,12 +1892,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 12, "test.source.start": 9 }, - "duration": 44542, - "start": 1727947161588404376 + "duration": 58000, + "start": 1733391663047749671 }], [ { @@ -1895,11 +1910,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1910,7 +1925,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1921,13 +1936,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -1941,9 +1956,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "12838910888505032284", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "5020060091297303190", "type": "test" }, "metrics": { @@ -1951,12 +1967,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 48, "test.source.start": 16 }, - "duration": 41917, - "start": 1727947161588510376 + "duration": 52458, + "start": 1733391663047872171 }], [ { @@ -1969,11 +1985,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1984,7 +2000,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1995,13 +2011,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2015,9 +2031,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "10686430323535585902", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "14887943326832864284", "type": "test" }, "metrics": { @@ -2025,12 +2042,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 6, "test.source.start": 4 }, - "duration": 44292, - "start": 1727947161588800459 + "duration": 47125, + "start": 1733391663048189296 }], [ { @@ -2043,11 +2060,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2058,7 +2075,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2069,13 +2086,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2089,9 +2106,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "10686430323535585902", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "14887943326832864284", "type": "test" }, "metrics": { @@ -2099,12 +2117,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 12, "test.source.start": 9 }, - "duration": 41000, - "start": 1727947161588907001 + "duration": 48250, + "start": 1733391663048306588 }], [ { @@ -2117,11 +2135,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2132,7 +2150,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2143,13 +2161,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2163,9 +2181,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "11737756619506818790", - "test_session_id": "203428879956679513", - "test_suite_id": "10686430323535585902", + "test.type": "test", + "test_module_id": "4528923178043536385", + "test_session_id": "4539691886584922642", + "test_suite_id": "14887943326832864284", "type": "test" }, "metrics": { @@ -2173,12 +2192,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 48, "test.source.start": 16 }, - "duration": 49792, - "start": 1727947161589009876 + "duration": 45958, + "start": 1733391663048422921 }], [ { @@ -2191,11 +2210,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2206,7 +2225,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2217,13 +2236,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2238,9 +2257,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "16841724294445300103", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "18297923163300115258", "type": "test" }, "metrics": { @@ -2248,12 +2268,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 6, "test.source.start": 4 }, - "duration": 48709, - "start": 1727947161589302834 + "duration": 55042, + "start": 1733391663048739879 }], [ { @@ -2266,11 +2286,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2281,7 +2301,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2292,13 +2312,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2313,9 +2333,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "16841724294445300103", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "18297923163300115258", "type": "test" }, "metrics": { @@ -2323,12 +2344,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 12, "test.source.start": 9 }, - "duration": 44708, - "start": 1727947161589413001 + "duration": 58000, + "start": 1733391663048859421 }], [ { @@ -2341,11 +2362,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2356,7 +2377,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2367,13 +2388,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2388,9 +2409,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "16841724294445300103", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "18297923163300115258", "type": "test" }, "metrics": { @@ -2398,12 +2420,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335, + "process_id": 32216, "test.source.end": 48, "test.source.start": 16 }, - "duration": 45709, - "start": 1727947161589519334 + "duration": 49791, + "start": 1733391663048978213 }], [ { @@ -2416,11 +2438,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2431,7 +2453,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2442,13 +2464,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2462,9 +2484,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_6", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "9128268971301319397", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "13895245717540955304", "type": "test" }, "metrics": { @@ -2472,10 +2495,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335 + "process_id": 32216 }, - "duration": 36708, - "start": 1727947161589724751 + "duration": 36792, + "start": 1733391663049205921 }], [ { @@ -2488,11 +2511,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2503,7 +2526,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2514,13 +2537,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2533,9 +2556,10 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_6", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "9128268971301319397", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "13895245717540955304", "type": "test" }, "metrics": { @@ -2543,10 +2567,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335 + "process_id": 32216 }, - "duration": 31042, - "start": 1727947161589820626 + "duration": 35042, + "start": 1733391663049319921 }], [ { @@ -2559,11 +2583,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_suite_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe619900000000", + "_dd.p.tid": "6751752f00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2574,7 +2598,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-96/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_suite_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2585,13 +2609,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "7916f91838314ee2a2565fb6c27182c7", + "runtime-id": "605326836a5f4cd58c13f88691f97aef", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_suite_level", "test.framework": "dd_manual_test_fw", @@ -2605,9 +2629,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_6", - "test_module_id": "9351361525684279631", - "test_session_id": "203428879956679513", - "test_suite_id": "9128268971301319397", + "test.type": "test", + "test_module_id": "5908865072567484213", + "test_session_id": "4539691886584922642", + "test_suite_id": "13895245717540955304", "type": "test" }, "metrics": { @@ -2615,8 +2640,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 94335 + "process_id": 32216 }, - "duration": 34250, - "start": 1727947161589911584 + "duration": 36459, + "start": 1733391663049410254 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json index 443ac792003..61556266702 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_fail_itr_test_level.json @@ -9,11 +9,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,13 +35,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -55,9 +55,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -65,12 +66,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 2, "test.source.start": 1 }, - "duration": 122667, - "start": 1727952526951257304 + "duration": 140041, + "start": 1733391723985893297 }], [ { @@ -83,11 +84,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -98,7 +99,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -109,13 +110,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -129,9 +130,10 @@ "test.status": "skip", "test.suite": "suite_1", "test.tag1": "suite_1_test_2_id", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -139,10 +141,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608 + "process_id": 32526 }, - "duration": 181333, - "start": 1727952526968284221 + "duration": 211208, + "start": 1733391724001779630 }], [ { @@ -155,11 +157,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -170,7 +172,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -181,13 +183,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_mix_fail_itr_test_level", @@ -203,9 +205,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -213,12 +216,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 12, "test.source.start": 4 }, - "duration": 129625, - "start": 1727952526968570554 + "duration": 133583, + "start": 1733391724002080755 }], [ { @@ -231,11 +234,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -246,7 +249,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -257,13 +260,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -279,9 +282,10 @@ "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_1_id", "test.tag2": "value_for_tag_2", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -289,11 +293,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.tag4": 4 }, - "duration": 73292, - "start": 1727952526968858471 + "duration": 82792, + "start": 1733391724002379755 }], [ { @@ -306,11 +310,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -321,7 +325,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -332,13 +336,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -352,9 +356,10 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -362,10 +367,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608 + "process_id": 32526 }, - "duration": 34583, - "start": 1727952526969001721 + "duration": 36583, + "start": 1733391724002527047 }], [ { @@ -378,11 +383,11 @@ "type": "test", "error": 1, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -393,10 +398,10 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "error.message": "This is a fake exception", - "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir/fake_runner_mix_fail_itr_test_level.py\", line 32, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", + "error.stack": "Traceback (most recent call last):\n File \"/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0/fake_runner_mix_fail_itr_test_level.py\", line 32, in _make_excinfo\n raise ValueError(\"This is a fake exception\")\nValueError: This is a fake exception\n", "error.type": "builtins.ValueError", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -407,13 +412,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -428,9 +433,10 @@ "test.status": "fail", "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_3_id", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test.type": "test", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test" }, "metrics": { @@ -438,11 +444,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.tag3": 12333333 }, - "duration": 157709, - "start": 1727952526969139304 + "duration": 201125, + "start": 1733391724002656088 }], [ { @@ -455,12 +461,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -471,7 +477,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -482,13 +488,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_test_level", @@ -501,7 +507,7 @@ "test.itr.unskippable": "true", "test.skipped_by_itr": "false", "test.status": "fail", - "test_session_id": "8935161898294122227", + "test_session_id": "12257644537884256232", "type": "test_session_end" }, "metrics": { @@ -509,11 +515,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.itr.tests_skipping.count": 8 }, - "duration": 22459667, - "start": 1727952526950614929 + "duration": 21031042, + "start": 1733391723985124255 }, { "name": "test_visibility.module", @@ -525,12 +531,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -541,7 +547,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -552,12 +558,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_test_level", @@ -572,8 +578,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "fail", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", "type": "test_module_end" }, "metrics": { @@ -582,8 +588,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 18325416, - "start": 1727952526951165263 + "duration": 17226083, + "start": 1733391723985832130 }, { "name": "test_visibility.suite", @@ -595,11 +601,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -610,7 +616,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -621,12 +627,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -639,9 +645,9 @@ "test.skipped_by_itr": "false", "test.status": "fail", "test.suite": "suite_1", - "test_module_id": "16326894050872258816", - "test_session_id": "8935161898294122227", - "test_suite_id": "7598533639884241886", + "test_module_id": "12402920523851005352", + "test_session_id": "12257644537884256232", + "test_suite_id": "10922862133282045719", "type": "test_suite_end" }, "metrics": { @@ -650,8 +656,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 18210542, - "start": 1727952526951190221 + "duration": 17101584, + "start": 1733391723985863213 }, { "name": "test_visibility.module", @@ -663,12 +669,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -679,7 +685,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -690,12 +696,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_test_level", @@ -710,8 +716,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "pass", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", "type": "test_module_end" }, "metrics": { @@ -720,8 +726,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 1566709, - "start": 1727952526969542804 + "duration": 1073250, + "start": 1733391724003111422 }, { "name": "test_visibility.suite", @@ -733,11 +739,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -748,7 +754,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -759,12 +765,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -777,9 +783,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test_suite_end" }, "metrics": { @@ -788,8 +794,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 1 }, - "duration": 1453667, - "start": 1727952526969565846 + "duration": 965833, + "start": 1733391724003140130 }, { "name": "test_visibility.module", @@ -801,12 +807,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "false", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -817,7 +823,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -828,12 +834,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_test_level", @@ -848,8 +854,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "fail", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", "type": "test_module_end" }, "metrics": { @@ -858,8 +864,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 841542, - "start": 1727952526971163179 + "duration": 854250, + "start": 1733391724004238213 }, { "name": "test_visibility.suite", @@ -871,11 +877,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -886,7 +892,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -897,12 +903,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -915,9 +921,9 @@ "test.skipped_by_itr": "false", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "2081847500774232892", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "11501717191105070619", "type": "test_suite_end" }, "metrics": { @@ -926,8 +932,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 351250, - "start": 1727952526971182971 + "duration": 363500, + "start": 1733391724004260713 }, { "name": "test_visibility.suite", @@ -939,11 +945,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -954,7 +960,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -965,12 +971,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -983,9 +989,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "9221908167091757770", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "6737582683225038685", "type": "test_suite_end" }, "metrics": { @@ -994,8 +1000,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 0 }, - "duration": 342583, - "start": 1727952526971585971 + "duration": 338958, + "start": 1733391724004675922 }, { "name": "test_visibility.module", @@ -1007,12 +1013,12 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.ci.itr.tests_skipped": "true", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1023,7 +1029,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1034,12 +1040,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_fail_itr_test_level", @@ -1054,8 +1060,8 @@ "test.module_path": "", "test.skipped_by_itr": "false", "test.status": "pass", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", "type": "test_module_end" }, "metrics": { @@ -1064,8 +1070,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 6 }, - "duration": 900583, - "start": 1727952526972053846 + "duration": 888750, + "start": 1733391724005150505 }, { "name": "test_visibility.suite", @@ -1077,11 +1083,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1092,7 +1098,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1103,12 +1109,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1122,9 +1128,9 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "15670090834955969272", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4684442297508797263", "type": "test_suite_end" }, "metrics": { @@ -1133,8 +1139,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 4 }, - "duration": 379250, - "start": 1727952526972079554 + "duration": 381166, + "start": 1733391724005172797 }, { "name": "test_visibility.suite", @@ -1146,11 +1152,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756b00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1161,7 +1167,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1172,12 +1178,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1190,9 +1196,9 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_6", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "5770223000073500038", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4096238897430036782", "type": "test_suite_end" }, "metrics": { @@ -1201,8 +1207,8 @@ "_sampling_priority_v1": 1, "test.itr.tests_skipping.count": 2 }, - "duration": 358792, - "start": 1727952526972516471 + "duration": 356750, + "start": 1733391724005606130 }], [ { @@ -1215,11 +1221,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1230,7 +1236,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1241,13 +1247,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1261,9 +1267,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1271,12 +1278,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 2, "test.source.start": 1 }, - "duration": 48833, - "start": 1727952526969585263 + "duration": 49375, + "start": 1733391724003161755 }], [ { @@ -1289,11 +1296,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1304,7 +1311,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1315,13 +1322,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1338,9 +1345,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_2_parametrized_1_id", "test.tag2": "two", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1348,13 +1356,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 9, "test.source.start": 8, "test.tag3": 3 }, - "duration": 46833, - "start": 1727952526969705846 + "duration": 45958, + "start": 1733391724003276672 }], [ { @@ -1367,11 +1375,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1382,7 +1390,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1393,13 +1401,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1414,9 +1422,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1424,12 +1433,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 9, "test.source.start": 8 }, - "duration": 53750, - "start": 1727952526969814554 + "duration": 66667, + "start": 1733391724003379088 }], [ { @@ -1442,11 +1451,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1457,7 +1466,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1468,13 +1477,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1490,9 +1499,10 @@ "test.source.file": "test_file_2.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1500,12 +1510,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 9, "test.source.start": 8 }, - "duration": 49834, - "start": 1727952526969929804 + "duration": 48834, + "start": 1733391724003503588 }], [ { @@ -1518,11 +1528,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1533,7 +1543,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1544,13 +1554,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1565,9 +1575,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1575,12 +1586,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 9, "test.source.start": 8 }, - "duration": 49500, - "start": 1727952526970040346 + "duration": 43292, + "start": 1733391724003610130 }], [ { @@ -1593,11 +1604,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1608,7 +1619,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1619,13 +1630,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1640,9 +1651,10 @@ "test.source.file": "test_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1650,12 +1662,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 9, "test.source.start": 8 }, - "duration": 641167, - "start": 1727952526970151929 + "duration": 183167, + "start": 1733391724003710380 }], [ { @@ -1668,11 +1680,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1683,7 +1695,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1694,13 +1706,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_mix_fail_itr_test_level", @@ -1717,9 +1729,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_3_id", "test.tag3": "this tag stays", - "test_module_id": "1144736915688735607", - "test_session_id": "8935161898294122227", - "test_suite_id": "3090686763628177673", + "test.type": "test", + "test_module_id": "17219085308137695083", + "test_session_id": "12257644537884256232", + "test_suite_id": "1235645924678503694", "type": "test" }, "metrics": { @@ -1727,13 +1740,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 12, "test.source.start": 4, "test.tag2": 2 }, - "duration": 65042, - "start": 1727952526970866554 + "duration": 62458, + "start": 1733391724003956672 }], [ { @@ -1746,11 +1759,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1761,7 +1774,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1772,13 +1785,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1792,9 +1805,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "2081847500774232892", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "11501717191105070619", "type": "test" }, "metrics": { @@ -1802,12 +1816,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 6, "test.source.start": 4 }, - "duration": 43375, - "start": 1727952526971201596 + "duration": 46583, + "start": 1733391724004281922 }], [ { @@ -1820,11 +1834,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1835,7 +1849,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1846,13 +1860,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1866,9 +1880,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "fail", "test.suite": "suite_3", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "2081847500774232892", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "11501717191105070619", "type": "test" }, "metrics": { @@ -1876,12 +1891,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 12, "test.source.start": 9 }, - "duration": 42416, - "start": 1727952526971308388 + "duration": 55584, + "start": 1733391724004389088 }], [ { @@ -1894,11 +1909,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1909,7 +1924,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1920,13 +1935,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -1940,9 +1955,10 @@ "test.source.file": "module_3/suite_3.py", "test.status": "pass", "test.suite": "suite_3", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "2081847500774232892", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "11501717191105070619", "type": "test" }, "metrics": { @@ -1950,12 +1966,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 48, "test.source.start": 16 }, - "duration": 42042, - "start": 1727952526971413346 + "duration": 42708, + "start": 1733391724004503922 }], [ { @@ -1968,11 +1984,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1983,7 +1999,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1994,13 +2010,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2014,9 +2030,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "9221908167091757770", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "6737582683225038685", "type": "test" }, "metrics": { @@ -2024,12 +2041,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 6, "test.source.start": 4 }, - "duration": 41541, - "start": 1727952526971605638 + "duration": 43750, + "start": 1733391724004697172 }], [ { @@ -2042,11 +2059,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2057,7 +2074,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2068,13 +2085,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2088,9 +2105,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "9221908167091757770", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "6737582683225038685", "type": "test" }, "metrics": { @@ -2098,12 +2116,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 12, "test.source.start": 9 }, - "duration": 40083, - "start": 1727952526971705888 + "duration": 43041, + "start": 1733391724004798422 }], [ { @@ -2116,11 +2134,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2131,7 +2149,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2142,13 +2160,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2162,9 +2180,10 @@ "test.source.file": "module_3/suite_4.py", "test.status": "pass", "test.suite": "suite_4", - "test_module_id": "5227100560773118461", - "test_session_id": "8935161898294122227", - "test_suite_id": "9221908167091757770", + "test.type": "test", + "test_module_id": "2533414071775315654", + "test_session_id": "12257644537884256232", + "test_suite_id": "6737582683225038685", "type": "test" }, "metrics": { @@ -2172,12 +2191,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 48, "test.source.start": 16 }, - "duration": 42167, - "start": 1727952526971806971 + "duration": 41709, + "start": 1733391724004897088 }], [ { @@ -2190,11 +2209,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2205,7 +2224,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2216,13 +2235,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2237,9 +2256,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "15670090834955969272", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4684442297508797263", "type": "test" }, "metrics": { @@ -2247,12 +2267,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 6, "test.source.start": 4 }, - "duration": 48791, - "start": 1727952526972098513 + "duration": 50083, + "start": 1733391724005193380 }], [ { @@ -2265,11 +2285,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2280,7 +2300,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2291,13 +2311,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2312,9 +2332,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "15670090834955969272", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4684442297508797263", "type": "test" }, "metrics": { @@ -2322,12 +2343,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 12, "test.source.start": 9 }, - "duration": 47416, - "start": 1727952526972208763 + "duration": 48500, + "start": 1733391724005304755 }], [ { @@ -2340,11 +2361,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2355,7 +2376,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2366,13 +2387,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2387,9 +2408,10 @@ "test.source.file": "module_5/suite_5.py", "test.status": "skip", "test.suite": "suite_5", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "15670090834955969272", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4684442297508797263", "type": "test" }, "metrics": { @@ -2397,12 +2419,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608, + "process_id": 32526, "test.source.end": 48, "test.source.start": 16 }, - "duration": 47333, - "start": 1727952526972319721 + "duration": 48583, + "start": 1733391724005417630 }], [ { @@ -2415,11 +2437,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2430,7 +2452,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2441,13 +2463,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2461,9 +2483,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_6", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "5770223000073500038", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4096238897430036782", "type": "test" }, "metrics": { @@ -2471,10 +2494,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608 + "process_id": 32526 }, - "duration": 37334, - "start": 1727952526972537679 + "duration": 37625, + "start": 1733391724005626672 }], [ { @@ -2487,11 +2510,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2502,7 +2525,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2513,13 +2536,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2532,9 +2555,10 @@ "test.skipped_by_itr": "false", "test.status": "pass", "test.suite": "suite_6", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "5770223000073500038", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4096238897430036782", "type": "test" }, "metrics": { @@ -2542,10 +2566,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608 + "process_id": 32526 }, - "duration": 61125, - "start": 1727952526972639346 + "duration": 65333, + "start": 1733391724005726505 }], [ { @@ -2558,11 +2582,11 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_fail_itr_test_level0", "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66fe768e00000000", + "_dd.p.tid": "6751756c00000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -2573,7 +2597,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-125/ddtrace_subprocess_dir", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_fail_itr_test_level0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -2584,13 +2608,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.15.0.dev27+g0e1f4eb55.d20240927", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "977633e471d64dccaf0fd70300d75aa9", + "runtime-id": "3543bf3d74124eb0b6b49faad852c55a", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_fail_itr_test_level", "test.framework": "dd_manual_test_fw", @@ -2604,9 +2628,10 @@ "test.skipped_by_itr": "true", "test.status": "skip", "test.suite": "suite_6", - "test_module_id": "2672080937724697394", - "test_session_id": "8935161898294122227", - "test_suite_id": "5770223000073500038", + "test.type": "test", + "test_module_id": "12131303412622054108", + "test_session_id": "12257644537884256232", + "test_suite_id": "4096238897430036782", "type": "test" }, "metrics": { @@ -2614,8 +2639,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 5608 + "process_id": 32526 }, - "duration": 35125, - "start": 1727952526972761471 + "duration": 38500, + "start": 1733391724005851672 }]] diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json index 5a1385bc16d..adb0c8389b4 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_runner_mix_pass.json @@ -9,20 +9,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -33,9 +33,10 @@ "test.source.file": "my_file_1.py", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -43,12 +44,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 2, "test.source.start": 1 }, - "duration": 136875, - "start": 1726064457059141376 + "duration": 77833, + "start": 1733391690722081462 }], [ { @@ -61,20 +62,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -85,9 +86,10 @@ "test.status": "skip", "test.suite": "suite_1", "test.tag1": "suite_1_test_2_id", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -95,10 +97,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172 + "process_id": 32402 }, - "duration": 73958, - "start": 1726064457076692668 + "duration": 83167, + "start": 1733391690736997753 }], [ { @@ -111,20 +113,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_test_mix_pass", @@ -136,9 +138,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_1", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -146,12 +149,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 12, "test.source.start": 4 }, - "duration": 66250, - "start": 1726064457076859043 + "duration": 61500, + "start": 1733391690737149795 }], [ { @@ -164,20 +167,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -190,9 +193,10 @@ "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_1_id", "test.tag2": "value_for_tag_2", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -200,11 +204,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.tag4": 4 }, - "duration": 59541, - "start": 1726064457076985710 + "duration": 60500, + "start": 1733391690737261587 }], [ { @@ -217,20 +221,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -241,9 +245,10 @@ "test.parameters": "{\"param1\": \"value2\"}", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -251,10 +256,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172 + "process_id": 32402 }, - "duration": 33375, - "start": 1726064457077100626 + "duration": 32083, + "start": 1733391690737370212 }], [ { @@ -267,20 +272,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -292,9 +297,10 @@ "test.status": "pass", "test.suite": "suite_1", "test.tag1": "suite_1_test_4_parametrized_3_id", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test.type": "test", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test" }, "metrics": { @@ -302,11 +308,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.tag3": 12333333 }, - "duration": 36542, - "start": 1726064457077216126 + "duration": 34709, + "start": 1733391690737473753 }], [ { @@ -319,20 +325,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_pass", @@ -340,7 +346,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "13874450661336061519", + "test_session_id": "3318079279205477734", "type": "test_session_end" }, "metrics": { @@ -348,10 +354,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172 + "process_id": 32402 }, - "duration": 19821958, - "start": 1726064457058710335 + "duration": 17030459, + "start": 1733391690721617003 }, { "name": "test_visibility.module", @@ -363,19 +369,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_pass", @@ -385,8 +391,8 @@ "test.module": "module_1", "test.module_path": "", "test.status": "pass", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", "type": "test_module_end" }, "metrics": { @@ -394,8 +400,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18344291, - "start": 1726064457059078960 + "duration": 15722334, + "start": 1733391690722019503 }, { "name": "test_visibility.suite", @@ -407,19 +413,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -428,9 +434,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_1", - "test_module_id": "6414613614171039050", - "test_session_id": "13874450661336061519", - "test_suite_id": "255176703224054224", + "test_module_id": "10156760375257616544", + "test_session_id": "3318079279205477734", + "test_suite_id": "5749543742977212779", "type": "test_suite_end" }, "metrics": { @@ -438,8 +444,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 18238125, - "start": 1726064457059106418 + "duration": 15609958, + "start": 1733391690722050420 }, { "name": "test_visibility.module", @@ -451,19 +457,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_test_mix_pass", @@ -473,8 +479,8 @@ "test.module": "module_2", "test.module_path": "", "test.status": "pass", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", "type": "test_module_end" }, "metrics": { @@ -482,8 +488,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 991791, - "start": 1726064457077468960 + "duration": 790083, + "start": 1733391690737784295 }, { "name": "test_visibility.suite", @@ -495,19 +501,19 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -516,9 +522,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test_suite_end" }, "metrics": { @@ -526,8 +532,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 904667, - "start": 1726064457077495376 + "duration": 706792, + "start": 1733391690737810003 }], [ { @@ -540,20 +546,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -564,9 +570,10 @@ "test.source.file": "my_file_1.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -574,12 +581,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 2, "test.source.start": 1 }, - "duration": 49417, - "start": 1726064457077516126 + "duration": 48792, + "start": 1733391690737831128 }], [ { @@ -592,20 +599,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -619,9 +626,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_2_parametrized_1_id", "test.tag2": "two", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -629,13 +637,13 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 9, "test.source.start": 8, "test.tag3": 3 }, - "duration": 48750, - "start": 1726064457077628126 + "duration": 44500, + "start": 1733391690737942795 }], [ { @@ -648,20 +656,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -673,9 +681,10 @@ "test.source.file": "my_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -683,12 +692,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 9, "test.source.start": 8 }, - "duration": 55375, - "start": 1726064457077727960 + "duration": 64000, + "start": 1733391690738033670 }], [ { @@ -701,20 +710,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -726,9 +735,10 @@ "test.source.file": "my_file_2.py", "test.status": "skip", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -736,12 +746,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 9, "test.source.start": 8 }, - "duration": 50584, - "start": 1726064457077845251 + "duration": 43291, + "start": 1733391690738142087 }], [ { @@ -754,20 +764,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -779,9 +789,10 @@ "test.source.file": "my_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -789,12 +800,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 9, "test.source.start": 8 }, - "duration": 44875, - "start": 1726064457077947376 + "duration": 41542, + "start": 1733391690738230545 }], [ { @@ -807,20 +818,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.command": "manual_test_mix_pass", "test.framework": "dd_manual_test_fw", @@ -832,9 +843,10 @@ "test.source.file": "my_file_2.py", "test.status": "pass", "test.suite": "suite_2", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -842,12 +854,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 9, "test.source.start": 8 }, - "duration": 81042, - "start": 1726064457078078376 + "duration": 40417, + "start": 1733391690738316128 }], [ { @@ -860,20 +872,20 @@ "type": "test", "error": 0, "meta": { - "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.base_service": "test_manual_api_fake_runner_mix_pass0", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1a74900000000", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-163/ddtrace_subprocess_dir", + "_dd.p.tid": "6751754a00000000", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_runner_mix_pass0", "component": "dd_manual_test_fw", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", + "library_version": "2.18.0.dev124+gc03b9e422.d20241205", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "cc6fe6c376c64634a9b0794ca017b26c", + "runtime-id": "eb4ad949d0774f749055ff641f5fd0db", "runtime.name": "CPython", - "runtime.version": "3.10.14", + "runtime.version": "3.9.19", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_test_mix_pass", @@ -887,9 +899,10 @@ "test.suite": "suite_2", "test.tag1": "suite_2_test_3_id", "test.tag3": "this tag stays", - "test_module_id": "1131995907502351038", - "test_session_id": "13874450661336061519", - "test_suite_id": "5124189333257608693", + "test.type": "test", + "test_module_id": "43247054266292789", + "test_session_id": "3318079279205477734", + "test_suite_id": "7422538890171200598", "type": "test" }, "metrics": { @@ -897,11 +910,11 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 96172, + "process_id": 32402, "test.source.end": 12, "test.source.start": 4, "test.tag2": 2 }, - "duration": 82166, - "start": 1726064457078239835 + "duration": 54375, + "start": 1733391690738399420 }]] diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json new file mode 100644 index 00000000000..c0d2e49e338 --- /dev/null +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json @@ -0,0 +1,187 @@ +[[ + { + "name": "pytest.test_session", + "service": "pytest", + "resource": "pytest.test_session", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a500000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "59d942ad4a7b4d07ba146936547f60c8", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.status": "pass", + "test_session_id": "13653929687187018380", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 13623 + }, + "duration": 1151463759, + "start": 1733243045205773568 + }, + { + "name": "pytest.test_module", + "service": "pytest", + "resource": "pytest.test_module", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a500000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test_module_id": "6093764858853198442", + "test_session_id": "13653929687187018380", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1035492200, + "start": 1733243045320630899 + }, + { + "name": "pytest.test_suite", + "service": "pytest", + "resource": "pytest.test_suite", + "trace_id": 0, + "span_id": 3, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a500000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test_module_id": "6093764858853198442", + "test_session_id": "13653929687187018380", + "test_suite_id": "1886109580838464934", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1018521912, + "start": 1733243045320749085 + }], +[ + { + "name": "pytest.test", + "service": "pytest", + "resource": "test_selenium.py::test_selenium_local_pass", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a500000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "59d942ad4a7b4d07ba146936547f60c8", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.browser.driver": "selenium", + "test.browser.driver_version": "4.27.1", + "test.browser.name": "chrome", + "test.browser.version": "131.0.6778.85", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.is_browser": "true", + "test.module": "", + "test.module_path": "", + "test.name": "test_selenium_local_pass", + "test.source.file": "test_selenium.py", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test.type": "test", + "test_module_id": "6093764858853198442", + "test_session_id": "13653929687187018380", + "test_suite_id": "1886109580838464934", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 13623, + "test.source.end": 29, + "test.source.start": 7 + }, + "duration": 1035420043, + "start": 1733243045320806200 + }]] diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json new file mode 100644 index 00000000000..ba89055adf9 --- /dev/null +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json @@ -0,0 +1,188 @@ +[[ + { + "name": "pytest.test_session", + "service": "pytest", + "resource": "pytest.test_session", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a200000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "7302c641201a429b97db4b8c863fc1e9", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.status": "pass", + "test_session_id": "11779449984696132842", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 13453 + }, + "duration": 1175555510, + "start": 1733243042959917377 + }, + { + "name": "pytest.test_module", + "service": "pytest", + "resource": "pytest.test_module", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a200000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test_module_id": "7190997582830612638", + "test_session_id": "11779449984696132842", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1056334178, + "start": 1733243043078015355 + }, + { + "name": "pytest.test_suite", + "service": "pytest", + "resource": "pytest.test_suite", + "trace_id": 0, + "span_id": 3, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a200000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test_module_id": "7190997582830612638", + "test_session_id": "11779449984696132842", + "test_suite_id": "16799388353789766894", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 1039485691, + "start": 1733243043078144147 + }], +[ + { + "name": "pytest.test", + "service": "pytest", + "resource": "test_selenium.py::test_selenium_local_pass", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f30a300000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "7302c641201a429b97db4b8c863fc1e9", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.browser.driver": "selenium", + "test.browser.driver_version": "4.27.1", + "test.browser.name": "chrome", + "test.browser.version": "131.0.6778.85", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.is_browser": "true", + "test.is_rum_active": "true", + "test.module": "", + "test.module_path": "", + "test.name": "test_selenium_local_pass", + "test.source.file": "test_selenium.py", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test.type": "test", + "test_module_id": "7190997582830612638", + "test_session_id": "11779449984696132842", + "test_suite_id": "16799388353789766894", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 13453, + "test.source.end": 29, + "test.source.start": 7 + }, + "duration": 1056245299, + "start": 1733243043078204615 + }]] diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json new file mode 100644 index 00000000000..cb0d96551a3 --- /dev/null +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json @@ -0,0 +1,182 @@ +[[ + { + "name": "pytest.test_session", + "service": "pytest", + "resource": "pytest.test_session", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f2f1000000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "e66954df61ae4986bcb0c73415619b22", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.status": "pass", + "test_session_id": "8637723180980132607", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 12386 + }, + "duration": 657416237, + "start": 1733242640628685892 + }, + { + "name": "pytest.test_module", + "service": "pytest", + "resource": "pytest.test_module", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f2f1000000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.itr.tests_skipping.enabled": "false", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test_module_id": "13788837375514495677", + "test_session_id": "8637723180980132607", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 534863711, + "start": 1733242640750128386 + }, + { + "name": "pytest.test_suite", + "service": "pytest", + "resource": "pytest.test_suite", + "trace_id": 0, + "span_id": 3, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f2f1000000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test_module_id": "13788837375514495677", + "test_session_id": "8637723180980132607", + "test_suite_id": "16610807573449005295", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 518011312, + "start": 1733242640750250412 + }], +[ + { + "name": "pytest.test", + "service": "pytest", + "resource": "test_selenium.py::test_selenium_local_unpatch", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "674f2f1000000000", + "component": "pytest", + "language": "python", + "library_version": "2.18.0.dev95+g5f76e34a0", + "os.architecture": "x86_64", + "os.platform": "Linux", + "os.version": "5.15.0-1071-aws", + "runtime-id": "e66954df61ae4986bcb0c73415619b22", + "runtime.name": "CPython", + "runtime.version": "3.10.14", + "span.kind": "test", + "test.command": "pytest --ddtrace -s --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.3.4", + "test.module": "", + "test.module_path": "", + "test.name": "test_selenium_local_unpatch", + "test.source.file": "test_selenium.py", + "test.status": "pass", + "test.suite": "test_selenium.py", + "test.type": "test", + "test_module_id": "13788837375514495677", + "test_session_id": "8637723180980132607", + "test_suite_id": "16610807573449005295", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 12386, + "test.source.end": 32, + "test.source.start": 9 + }, + "duration": 534785315, + "start": 1733242640750308571 + }]] diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json index da0c84ec304..aaae7a11593 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_will_include_lines_pct.json @@ -11,29 +11,30 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983700000000", + "_dd.p.tid": "6751748100000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "78d09d63b5fc41798a7d18e8f242544c", + "os.version": "5.15.0-1071-aws", + "runtime-id": "5714e89371d3405f970eb00f53936a67", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.name": "test_add_two_number_list", "test.source.file": "test_tools.py", "test.status": "pass", "test.suite": "test_tools.py", - "test_module_id": "11721341866822824008", - "test_session_id": "9064411514598117610", - "test_suite_id": "5167954309994084382", + "test.type": "test", + "test_module_id": "18268695399323445148", + "test_session_id": "15721560387983696792", + "test_suite_id": "5983435943999745407", "type": "test" }, "metrics": { @@ -41,12 +42,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71510, + "process_id": 35337, "test.source.end": 9, "test.source.start": 3 }, - "duration": 1346167, - "start": 1726060599630523299 + "duration": 2404850, + "start": 1733391489340338256 }], [ { @@ -61,24 +62,24 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983700000000", + "_dd.p.tid": "6751748100000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "78d09d63b5fc41798a7d18e8f242544c", + "os.version": "5.15.0-1071-aws", + "runtime-id": "5714e89371d3405f970eb00f53936a67", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "9064411514598117610", + "test_session_id": "15721560387983696792", "type": "test_session_end" }, "metrics": { @@ -86,10 +87,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71510 + "process_id": 35337 }, - "duration": 22941583, - "start": 1726060599610872883 + "duration": 45677337, + "start": 1733391489299231481 }, { "name": "pytest.test_module", @@ -103,26 +104,26 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983700000000", + "_dd.p.tid": "6751748100000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.module": "", "test.module_path": "", "test.status": "pass", - "test_module_id": "11721341866822824008", - "test_session_id": "9064411514598117610", + "test_module_id": "18268695399323445148", + "test_session_id": "15721560387983696792", "type": "test_module_end" }, "metrics": { @@ -130,8 +131,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 2321875, - "start": 1726060599630423299 + "duration": 4050622, + "start": 1733391489340123850 }, { "name": "pytest.test_suite", @@ -145,26 +146,26 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983700000000", + "_dd.p.tid": "6751748100000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.status": "pass", "test.suite": "test_tools.py", - "test_module_id": "11721341866822824008", - "test_session_id": "9064411514598117610", - "test_suite_id": "5167954309994084382", + "test_module_id": "18268695399323445148", + "test_session_id": "15721560387983696792", + "test_suite_id": "5983435943999745407", "type": "test_suite_end" }, "metrics": { @@ -172,6 +173,6 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 2106000, - "start": 1726060599630479799 + "duration": 3652661, + "start": 1733391489340244453 }]] diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_with_ddtrace_patch_all.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_with_ddtrace_patch_all.json index 6a708352666..ee2bdad3c32 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_with_ddtrace_patch_all.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_with_ddtrace_patch_all.json @@ -12,29 +12,30 @@ "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983a00000000", + "_dd.p.tid": "6751748300000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "7023a31b98954b289ca15a7e4688a6d5", + "os.version": "5.15.0-1071-aws", + "runtime-id": "5c3c14b20446491cab88eeaab4c7b2ca", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace --ddtrace-patch-all", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.name": "test_call_urllib", "test.source.file": "test_call_httpx.py", "test.status": "pass", "test.suite": "test_call_httpx.py", - "test_module_id": "15947526308210149980", - "test_session_id": "17743151003972535536", - "test_suite_id": "18237177335340295089", + "test.type": "test", + "test_module_id": "761639678295786665", + "test_session_id": "7669351113286434020", + "test_suite_id": "3915890991912653435", "type": "test" }, "metrics": { @@ -42,12 +43,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71547, + "process_id": 35397, "test.source.end": 6, "test.source.start": 3 }, - "duration": 59740250, - "start": 1726060602879991384 + "duration": 48804281, + "start": 1733391491768908928 }, { "name": "http.request", @@ -62,17 +63,17 @@ "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983a00000000", + "_dd.p.tid": "6751748300000000", "component": "httpx", "http.method": "GET", "http.status_code": "404", "http.url": "http://localhost:9126/bad_path.cgi", - "http.useragent": "python-httpx/0.27.2", + "http.useragent": "python-httpx/0.27.0", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "out.host": "localhost", "runtime.name": "CPython", "runtime.version": "3.10.14", @@ -84,8 +85,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 13890500, - "start": 1726060602914641801 + "duration": 2827364, + "start": 1733391491797337532 }], [ { @@ -101,24 +102,24 @@ "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983a00000000", + "_dd.p.tid": "6751748300000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "7023a31b98954b289ca15a7e4688a6d5", + "os.version": "5.15.0-1071-aws", + "runtime-id": "5c3c14b20446491cab88eeaab4c7b2ca", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace --ddtrace-patch-all", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "17743151003972535536", + "test_session_id": "7669351113286434020", "type": "test_session_end" }, "metrics": { @@ -126,10 +127,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71547 + "process_id": 35397 }, - "duration": 104630083, - "start": 1726060602836121176 + "duration": 137584749, + "start": 1733391491681458772 }, { "name": "pytest.test_module", @@ -144,26 +145,26 @@ "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983a00000000", + "_dd.p.tid": "6751748300000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace --ddtrace-patch-all", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.module": "", "test.module_path": "", "test.status": "pass", - "test_module_id": "15947526308210149980", - "test_session_id": "17743151003972535536", + "test_module_id": "761639678295786665", + "test_session_id": "7669351113286434020", "type": "test_module_end" }, "metrics": { @@ -171,8 +172,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 60049291, - "start": 1726060602879920051 + "duration": 49680714, + "start": 1733391491768751504 }, { "name": "pytest.test_suite", @@ -187,26 +188,26 @@ "_dd.base_service": "", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1983a00000000", + "_dd.p.tid": "6751748300000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace --ddtrace-patch-all", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.status": "pass", "test.suite": "test_call_httpx.py", - "test_module_id": "15947526308210149980", - "test_session_id": "17743151003972535536", - "test_suite_id": "18237177335340295089", + "test_module_id": "761639678295786665", + "test_session_id": "7669351113286434020", + "test_suite_id": "3915890991912653435", "type": "test_suite_end" }, "metrics": { @@ -214,6 +215,6 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 59928084, - "start": 1726060602879959842 + "duration": 49410722, + "start": 1733391491768837977 }]] diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json index ff002effa36..e2639071a83 100644 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot_v2.test_pytest_wont_include_lines_pct_if_report_empty.json @@ -11,29 +11,30 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1984b00000000", + "_dd.p.tid": "6751748200000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "d5419061e84547fb85bdc1ae97602e8a", + "os.version": "5.15.0-1071-aws", + "runtime-id": "b61780caa8904faabf04512907714231", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.name": "test_add_two_number_list", "test.source.file": "test_tools.py", "test.status": "pass", "test.suite": "test_tools.py", - "test_module_id": "17545732228742618091", - "test_session_id": "600848759226461151", - "test_suite_id": "14153921443993547686", + "test.type": "test", + "test_module_id": "7119982366821952773", + "test_session_id": "13474000765526278876", + "test_suite_id": "598508541787492892", "type": "test" }, "metrics": { @@ -41,12 +42,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71574, + "process_id": 35367, "test.source.end": 9, "test.source.start": 3 }, - "duration": 1835125, - "start": 1726060619773180045 + "duration": 2359278, + "start": 1733391490745608130 }], [ { @@ -61,24 +62,24 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1984b00000000", + "_dd.p.tid": "6751748200000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", - "runtime-id": "d5419061e84547fb85bdc1ae97602e8a", + "os.version": "5.15.0-1071-aws", + "runtime-id": "b61780caa8904faabf04512907714231", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "600848759226461151", + "test_session_id": "13474000765526278876", "type": "test_session_end" }, "metrics": { @@ -86,10 +87,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 71574 + "process_id": 35367 }, - "duration": 38400625, - "start": 1726060619738860211 + "duration": 44886230, + "start": 1733391490705175511 }, { "name": "pytest.test_module", @@ -103,26 +104,26 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1984b00000000", + "_dd.p.tid": "6751748200000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.itr.tests_skipping.enabled": "false", "test.module": "", "test.module_path": "", "test.status": "pass", - "test_module_id": "17545732228742618091", - "test_session_id": "600848759226461151", + "test_module_id": "7119982366821952773", + "test_session_id": "13474000765526278876", "type": "test_module_end" }, "metrics": { @@ -130,8 +131,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 3060500, - "start": 1726060619773011295 + "duration": 3942347, + "start": 1733391490745401240 }, { "name": "pytest.test_suite", @@ -145,26 +146,26 @@ "meta": { "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "66e1984b00000000", + "_dd.p.tid": "6751748200000000", "component": "pytest", "language": "python", - "library_version": "2.13.0.dev177+g25e628f0e.d20240910", - "os.architecture": "aarch64", + "library_version": "2.18.0.dev124+gc03b9e422", + "os.architecture": "x86_64", "os.platform": "Linux", - "os.version": "6.6.12-linuxkit", + "os.version": "5.15.0-1071-aws", "runtime.name": "CPython", "runtime.version": "3.10.14", "span.kind": "test", "test.command": "pytest --ddtrace", "test.framework": "pytest", - "test.framework_version": "8.3.2", + "test.framework_version": "6.2.5", "test.module": "", "test.module_path": "", "test.status": "pass", "test.suite": "test_tools.py", - "test_module_id": "17545732228742618091", - "test_session_id": "600848759226461151", - "test_suite_id": "14153921443993547686", + "test_module_id": "7119982366821952773", + "test_session_id": "13474000765526278876", + "test_suite_id": "598508541787492892", "type": "test_suite_end" }, "metrics": { @@ -172,6 +173,6 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 2787041, - "start": 1726060619773107920 + "duration": 3555862, + "start": 1733391490745516387 }]] From 54ab4f57fb9d4e582d86ae616c7ad13cf61c81f5 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:46:59 +0000 Subject: [PATCH 263/372] chore(ci_visibility): update RUM flush env var release (#11641) This was released in 2.18 , not 2.19. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 504440348fe..556f72d40a3 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -607,7 +607,7 @@ Test Visibility asynchronous function finishes before the driver is closed. version_added: - v2.19.0: + v2.18.0: Agent ----- From d234cdf2b03fc4b05d2c7305b1c689f34fe64774 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 9 Dec 2024 13:21:56 -0500 Subject: [PATCH 264/372] chore(profiling): simplify usage of cancellation token (#11644) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/include/uploader.hpp | 4 +++- .../datadog/profiling/dd_wrapper/src/uploader.cpp | 9 ++++----- tests/profiling_v2/collector/test_threading.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp index ed19f316fc3..8a5394b0cb2 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp @@ -24,7 +24,9 @@ class Uploader private: static inline std::mutex upload_lock{}; std::string errmsg; - static inline std::unique_ptr cancel; + static inline std::unique_ptr cancel{ + ddog_CancellationToken_new() + }; static inline std::atomic upload_seq{ 0 }; std::string output_filename; std::unique_ptr ddog_exporter; diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp index 3e63b97eb07..1e04a45fb41 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp @@ -115,10 +115,9 @@ Datadog::Uploader::upload(ddog_prof_Profile& profile) // Create a new cancellation token. Maybe we can get away without doing this, but // since we're recreating the uploader fresh every time anyway, we recreate one more thing. // NB wrapping this in a unique_ptr to easily add RAII semantics; maybe should just wrap it in a - // class instead - cancel.reset(ddog_CancellationToken_new()); - std::unique_ptr cancel_for_request; - cancel_for_request.reset(ddog_CancellationToken_clone(cancel.get())); + // class instead. + std::unique_ptr cancel_for_request( + ddog_CancellationToken_clone(cancel.get())); // The upload operation sets up some global state in libdatadog (the tokio runtime), so // we ensure exclusivity here. @@ -157,7 +156,7 @@ Datadog::Uploader::unlock() void Datadog::Uploader::cancel_inflight() { - cancel.reset(); + ddog_CancellationToken_cancel(cancel.get()); } void diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index abed7d0cfda..5ca09dd8da5 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -83,7 +83,7 @@ def test_patch(): @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="only works on linux") -@pytest.mark.subprocess() +@pytest.mark.subprocess(err=None) def test_user_threads_have_native_id(): from os import getpid from threading import Thread From cbd765bf1470f2081d4f66870a79812b24d5af6b Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 9 Dec 2024 14:36:17 -0500 Subject: [PATCH 265/372] chore(profiling): update fork death native test (#11628) - Use pthread instead of `std::thread` as std one doesn't properly clean up after itself, leading to leak sanitizer complaints - Also, join sampler threads to gracefully clean them up ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .../dd_wrapper/test/test_forking.cpp | 105 ++++++++++-------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp index 1c5ecb322c2..1aaadd62b61 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/test_forking.cpp @@ -3,12 +3,15 @@ #include #include +#include // Initiate an upload in a separate thread, otherwise we won't be mid-upload during fork -void -upload_in_thread() +void* +upload_in_thread(void*) { - std::thread([&]() { ddup_upload(); }).detach(); + ddup_upload(); + + return nullptr; } [[noreturn]] void @@ -24,6 +27,7 @@ profile_in_child(unsigned int num_threads, unsigned int run_time_ns, std::atomic launch_samplers(ids, 10e3, new_threads, done); std::this_thread::sleep_for(std::chrono::nanoseconds(run_time_ns)); done.store(true); + join_samplers(new_threads, done); ddup_upload(); std::exit(0); } @@ -34,24 +38,66 @@ is_exit_normal(int status) return WIFEXITED(status) && WEXITSTATUS(status) == 0; } +struct EmulateSamplerArg +{ + unsigned int id; + unsigned int sleep_time_ns; + std::atomic* done; +}; + +void* +emulate_sampler_wrapper(void* argp) +{ + EmulateSamplerArg* arg = reinterpret_cast(argp); + + emulate_sampler(arg->id, arg->sleep_time_ns, *arg->done); + + return nullptr; +} + +void +launch_pthread_samplers(std::vector& args, std::vector& pthread_handles) +{ + for (unsigned int i = 0; i < args.size(); i++) { + pthread_create(&pthread_handles[i], nullptr, emulate_sampler_wrapper, reinterpret_cast(&args[i])); + } +} + +void +join_pthread_samplers(std::vector& threads, std::atomic& done) +{ + done.store(true); + for (auto& handle : threads) { + pthread_join(handle, nullptr); + } +} + // Validates that sampling/uploads work around forks void sample_in_threads_and_fork(unsigned int num_threads, unsigned int sleep_time_ns) { configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); std::atomic done(false); - std::vector threads; + std::vector thread_handles; std::vector ids; - for (unsigned int i = 0; i < num_threads; i++) { - ids.push_back(i); + std::vector args; + + for (unsigned int i = 0; i < ids.size(); i++) { + auto id = ids[i]; + args.push_back(EmulateSamplerArg{ id, sleep_time_ns, &done }); } // ddup is configured, launch threads - launch_samplers(ids, sleep_time_ns, threads, done); + launch_pthread_samplers(args, thread_handles); // Collect some profiling data for a few ms, then upload in a thread before forking std::this_thread::sleep_for(std::chrono::milliseconds(500)); - upload_in_thread(); + + pthread_t thread; + if (pthread_create(&thread, nullptr, upload_in_thread, nullptr) != 0) { + std::cout << "failed to create thread" << std::endl; + std::exit(1); + } // Fork, wait in the parent for child to finish pid_t pid = fork(); @@ -60,57 +106,20 @@ sample_in_threads_and_fork(unsigned int num_threads, unsigned int sleep_time_ns) profile_in_child(num_threads, 500e3, done); // Child profiles for 500ms } + pthread_join(thread, nullptr); + // Parent int status; done.store(true); waitpid(pid, &status, 0); ddup_upload(); + join_pthread_samplers(thread_handles, done); if (!is_exit_normal(status)) { std::exit(1); } std::exit(0); } -// Really try to break things with many forks -void -fork_stress_test(unsigned int num_threads, unsigned int sleep_time_ns, unsigned int num_children) -{ - configure("my_test_service", "my_test_env", "0.0.1", "https://127.0.0.1:9126", "cpython", "3.10.6", "3.100", 256); - std::atomic done(false); - std::vector threads; - std::vector ids; - for (unsigned int i = 0; i < num_threads; i++) { - ids.push_back(i); - } - - // ddup is configured, launch threads - launch_samplers(ids, sleep_time_ns, threads, done); - - std::vector children; - upload_in_thread(); - while (num_children > 0) { - pid_t pid = fork(); - if (pid == 0) { - // Child - profile_in_child(num_threads, 500e3, done); // Child profiles for 500ms - } - children.push_back(pid); - num_children--; - } - - // Parent - int status; - done.store(true); - upload_in_thread(); - for (pid_t pid : children) { - waitpid(pid, &status, 0); - if (!is_exit_normal(status)) { - std::exit(1); - } - } - std::exit(0); -} - TEST(ForkDeathTest, SampleInThreadsAndForkNormal) { // Same memory leak as before--whatever From dcfaed47a7a2b619d3c2990a830fc7166d24b9ad Mon Sep 17 00:00:00 2001 From: Wassim Dhif Date: Mon, 9 Dec 2024 21:05:59 +0100 Subject: [PATCH 266/372] feat(origin detection): implement new spec (#9622) ### What does this PR do? Implements the new Origin Detection spec. ### Motivation This is needed to improve Origin Detection with specific edges cases such as Kata Containers while also improving both backward compatibility and future-proofness. ### Additional Notes This change were already discussed internally as part of the RFC regarding New Origin Detection Spec. ### Possible Drawbacks / Trade-offs None ### Describe how to test/QA your changes Unit tests were either modified or added to reflect changes. ### Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ### Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Signed-off-by: Wassim DHIF --- ddtrace/debugging/_uploader.py | 1 + ddtrace/internal/constants.py | 6 +++++- ddtrace/internal/processor/stats.py | 1 + ddtrace/internal/remoteconfig/client.py | 1 + ddtrace/internal/runtime/container.py | 16 +++++++++++++++- ddtrace/internal/telemetry/writer.py | 1 + ddtrace/internal/writer/writer.py | 1 + ddtrace/profiling/exporter/http.py | 1 + tests/integration/test_integration.py | 22 +++++++++++++++++++++- 9 files changed, 47 insertions(+), 3 deletions(-) diff --git a/ddtrace/debugging/_uploader.py b/ddtrace/debugging/_uploader.py index f2a3dae8d68..c6ff84fc190 100644 --- a/ddtrace/debugging/_uploader.py +++ b/ddtrace/debugging/_uploader.py @@ -56,6 +56,7 @@ def __init__(self, interval: Optional[float] = None) -> None: } container.update_headers_with_container_info(self._headers, container.get_container_info()) + container.update_header_with_external_info(self._headers) if di_config._tags_in_qs and di_config.tags: self.ENDPOINT += f"?ddtags={quote(di_config.tags)}" diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index bb55e657204..4efdc754ef3 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -57,9 +57,13 @@ REQUEST_PATH_PARAMS = "http.request.path_params" STATUS_403_TYPE_AUTO = {"status_code": 403, "type": "auto"} -ENTITY_ID_HEADER_NAME = "Datadog-Entity-ID" CONTAINER_ID_HEADER_NAME = "Datadog-Container-Id" +ENTITY_ID_HEADER_NAME = "Datadog-Entity-ID" + +EXTERNAL_ENV_HEADER_NAME = "Datadog-External-Env" +EXTERNAL_ENV_ENVIRONMENT_VARIABLE = "DD_EXTERNAL_ENV" + MESSAGING_SYSTEM = "messaging.system" FLASK_ENDPOINT = "flask.endpoint" diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index d24f0eae585..1ba8e008105 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -109,6 +109,7 @@ def __init__(self, agent_url, interval=None, timeout=1.0, retry_attempts=3): "Content-Type": "application/msgpack", } # type: Dict[str, str] container.update_headers_with_container_info(self._headers, container.get_container_info()) + container.update_header_with_external_info(self._headers) self._hostname = "" if config._report_hostname: self._hostname = get_hostname() diff --git a/ddtrace/internal/remoteconfig/client.py b/ddtrace/internal/remoteconfig/client.py index 014ba7e97e0..3f9315d0be0 100644 --- a/ddtrace/internal/remoteconfig/client.py +++ b/ddtrace/internal/remoteconfig/client.py @@ -241,6 +241,7 @@ def __init__(self) -> None: self._headers.update(parse_tags_str(additional_header_str)) container.update_headers_with_container_info(self._headers, container.get_container_info()) + container.update_header_with_external_info(self._headers) tags = ddtrace.config.tags.copy() diff --git a/ddtrace/internal/runtime/container.py b/ddtrace/internal/runtime/container.py index 146869b2661..c12a091fe21 100644 --- a/ddtrace/internal/runtime/container.py +++ b/ddtrace/internal/runtime/container.py @@ -7,6 +7,8 @@ from ..constants import CONTAINER_ID_HEADER_NAME from ..constants import ENTITY_ID_HEADER_NAME +from ..constants import EXTERNAL_ENV_ENVIRONMENT_VARIABLE +from ..constants import EXTERNAL_ENV_HEADER_NAME from ..logger import get_logger @@ -160,13 +162,14 @@ def get_container_info(pid="self"): def update_headers_with_container_info(headers: Dict, container_info: Optional[CGroupInfo]) -> None: + """Get the container info (either the container ID or the cgroup inode) and add it to the headers.""" if container_info is None: return if container_info.container_id: headers.update( { CONTAINER_ID_HEADER_NAME: container_info.container_id, - ENTITY_ID_HEADER_NAME: f"cid-{container_info.container_id}", + ENTITY_ID_HEADER_NAME: f"ci-{container_info.container_id}", } ) elif container_info.node_inode: @@ -175,3 +178,14 @@ def update_headers_with_container_info(headers: Dict, container_info: Optional[C ENTITY_ID_HEADER_NAME: f"in-{container_info.node_inode}", } ) + + +def update_header_with_external_info(headers: Dict) -> None: + """Get the external environment info from the environment variable and add it to the headers.""" + external_info = os.environ.get(EXTERNAL_ENV_ENVIRONMENT_VARIABLE) + if external_info: + headers.update( + { + EXTERNAL_ENV_HEADER_NAME: external_info, + } + ) diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 08171ce3b1a..899c77c1108 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -137,6 +137,7 @@ def get_headers(self, request): headers["DD-Telemetry-Request-Type"] = request["request_type"] headers["DD-Telemetry-API-Version"] = request["api_version"] container.update_headers_with_container_info(headers, container.get_container_info()) + container.update_header_with_external_info(headers) return headers def get_endpoint(self, agentless: bool) -> str: diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index f628daf8f8e..59eb56bd821 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -495,6 +495,7 @@ def __init__( _headers.update(headers) self._container_info = container.get_container_info() container.update_headers_with_container_info(_headers, self._container_info) + container.update_header_with_external_info(_headers) _headers.update({"Content-Type": client.encoder.content_type}) # type: ignore[attr-defined] additional_header_str = os.environ.get("_DD_TRACE_WRITER_ADDITIONAL_HEADERS") diff --git a/ddtrace/profiling/exporter/http.py b/ddtrace/profiling/exporter/http.py index 27dcf9be87b..f23e12acb5a 100644 --- a/ddtrace/profiling/exporter/http.py +++ b/ddtrace/profiling/exporter/http.py @@ -184,6 +184,7 @@ def export( headers = {} container.update_headers_with_container_info(headers, self._container_info) + container.update_header_with_external_info(headers) profile, libs = super(PprofHTTPExporter, self).export(events, start_time_ns, end_time_ns) pprof = io.BytesIO() diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 6bf9e80faca..78606bbde14 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -431,7 +431,7 @@ def test_validate_headers_in_payload_to_intake(): if container.get_container_info(): assert "Datadog-Container-Id" in headers assert "Datadog-Entity-ID" in headers - assert headers["Datadog-Entity-ID"].startswith("cid") + assert headers["Datadog-Entity-ID"].startswith("ci") @skip_if_testagent @@ -452,6 +452,26 @@ def test_inode_entity_id_header_present(): assert headers["Datadog-Entity-ID"].startswith("in") +@skip_if_testagent +@parametrize_with_all_encodings +def test_external_env_header_present(): + import mock + + from ddtrace import tracer as t + + mocked_external_env = "it-false,cn-nginx-webserver,pu-75a2b6d5-3949-4afb-ad0d-92ff0674e759" + + t._writer._put = mock.Mock(wraps=t._writer._put) + with mock.patch("os.environ.get") as oegmock: + oegmock.return_value = mocked_external_env + t.trace("op").finish() + t.shutdown() + assert t._writer._put.call_count == 1 + headers = t._writer._put.call_args[0][1] + assert "Datadog-External-Env" in headers + assert headers["Datadog-External-Env"] == mocked_external_env + + @skip_if_testagent @parametrize_with_all_encodings def test_validate_headers_in_payload_to_intake_with_multiple_traces(): From 32325312334374dd88bdd8c23f5d527fe730c991 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 9 Dec 2024 15:53:14 -0500 Subject: [PATCH 267/372] ci(profiling): gh action to run native tests with sanitizers (#11623) 1. Adds a GitHub action running profiling native tests with {thread, safety} sanitizers 2. Adds Thread sanitizer suppressions list 3. Updates echion commit to point to `taegyunkim/algorithm-include` from `taegyunkim/macos-fix`, https://github.com/taegyunkim/echion/compare/taegyunkim/macos-fix...taegyunkim:echion:taegyunkim/algorithm-include This change was necessary to compile native tests on the actions ubuntu runner. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .github/workflows/profiling-native.yml | 48 +++++++++++++++++++ .../profiling/dd_wrapper/test/CMakeLists.txt | 7 ++- .../profiling/dd_wrapper/test/TSan.supp | 4 ++ .../datadog/profiling/stack_v2/CMakeLists.txt | 2 +- 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/profiling-native.yml create mode 100644 ddtrace/internal/datadog/profiling/dd_wrapper/test/TSan.supp diff --git a/.github/workflows/profiling-native.yml b/.github/workflows/profiling-native.yml new file mode 100644 index 00000000000..98722552dbd --- /dev/null +++ b/.github/workflows/profiling-native.yml @@ -0,0 +1,48 @@ +name: Profiling Native Tests with Sanitizers + +on: + push: + branches: + - main + - "mq-working-branch**" + pull_request: + paths: + - ddtrace/internal/datadog/profiling/** + - ddtrace/profiling/** + workflow_dispatch: {} + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + sanitizer: ["safety", "thread"] + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 1 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install llvm 19 + run: | + # Ubuntu-24.04 GH actions image has llvm-18, but we use 19 as it's + # the latest one available. + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 + + - name: Run tests with sanitizers + run: | + # DEV: We currently have tests in dd_wrapper and stack_v2, setting + # stack_v2 here will also run tests in dd_wrapper. Revisit this when + # that changes. + ./ddtrace/internal/datadog/profiling/build_standalone.sh --${{matrix.sanitizer}} RelWithDebInfo stack_v2_test diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index 6a1e7f406f6..0be6098cd2a 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -21,7 +21,12 @@ function(dd_wrapper_add_test name) target_link_libraries(${name} PRIVATE gmock gtest_main dd_wrapper nlohmann_json::nlohmann_json) add_ddup_config(${name}) - gtest_discover_tests(${name}) + gtest_discover_tests(${name} + PROPERTIES + # We start new threads after fork(), and we want to continue + # running the tests after that instead of dying. + ENVIRONMENT "TSAN_OPTIONS=die_after_fork=0:suppressions=${CMAKE_CURRENT_SOURCE_DIR}/TSan.supp" + ) set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/..") diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/TSan.supp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/TSan.supp new file mode 100644 index 00000000000..bc9e9d23668 --- /dev/null +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/TSan.supp @@ -0,0 +1,4 @@ +# libdd is not compiled with sanitizers so probably that's why we get these +# data races from ddog_ArrayQueue and Datadog::Sample operations +race:ddog_ArrayQueue +race:Datadog::Sample:: diff --git a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt index 9fc78ee8046..50c8d056c79 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt @@ -37,7 +37,7 @@ endif() # Add echion set(ECHION_COMMIT - "9d5bcc5867d7aefff73c837adcba4ef46eecebc6" + "ed744987f224fae3f93c842b2b5ffb083984ff8b" CACHE STRING "Commit hash of echion to use") FetchContent_Declare( echion From 5bc8f8185fe6656633f968664e9a65a31b82df5e Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 9 Dec 2024 16:45:00 -0500 Subject: [PATCH 268/372] fix(crashtracking): resolve issue with zombie processes being behind (#11547) Co-authored-by: Taegyun Kim Co-authored-by: Taegyun Kim --- .../datadog/profiling/cmake/FindLibdatadog.cmake | 2 +- .../profiling/cmake/tools/libdatadog_checksums.txt | 10 +++++----- .../fix-crashtracking-zombie-aa81dc4efa96898d.yaml | 4 ++++ src/core/Cargo.lock | 4 ++-- src/core/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/fix-crashtracking-zombie-aa81dc4efa96898d.yaml diff --git a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake index c74851b9e65..6e103fe7d70 100644 --- a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake @@ -5,7 +5,7 @@ endif() include(ExternalProject) set(TAG_LIBDATADOG - "v14.1.0" + "v14.3.1" CACHE STRING "libdatadog github tag") set(Datadog_BUILD_DIR ${CMAKE_BINARY_DIR}/libdatadog) diff --git a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt index a6a65ce0f90..ca856e996ae 100644 --- a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt +++ b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt @@ -1,5 +1,5 @@ -fc6be3383d3a115804c43e2c66dd176c63f33b362d987d9b1211034e2b549c2d libdatadog-aarch64-alpine-linux-musl.tar.gz -b9c972afea19696ee6a459d2fa65563b738baf77dcb12739c8e4ae44d1c975fb libdatadog-aarch64-apple-darwin.tar.gz -1a9bc4d99d23f7baf403b6b7527f9b9d76bdb166dc34656150561dcb148cc90b libdatadog-aarch64-unknown-linux-gnu.tar.gz -8244831681332dfa939eefe6923fe6a8beaffff48cb336f836b55a438078add1 libdatadog-x86_64-alpine-linux-musl.tar.gz -76fcb3bfe3b3971d77f6dd4968ffe6bd5f6a1ada82e2e990a78919107dc2ee40 libdatadog-x86_64-unknown-linux-gnu.tar.gz +57f83aff275628bb1af89c22bb4bd696726daf2a9e09b6cd0d966b29e65a7ad6 libdatadog-aarch64-alpine-linux-musl.tar.gz +2be2efa98dfc32f109abdd79242a8e046a7a300c77634135eb293e000ecd4a4c libdatadog-aarch64-apple-darwin.tar.gz +36db8d50ccabb71571158ea13835c0f1d05d30b32135385f97c16343cfb6ddd4 libdatadog-aarch64-unknown-linux-gnu.tar.gz +2f61fd21cf2f8147743e414b4a8c77250a17be3aecc42a69ffe54f0a603d5c92 libdatadog-x86_64-alpine-linux-musl.tar.gz +f01f05600591063eba4faf388f54c155ab4e6302e5776c7855e3734955f7daf7 libdatadog-x86_64-unknown-linux-gnu.tar.gz diff --git a/releasenotes/notes/fix-crashtracking-zombie-aa81dc4efa96898d.yaml b/releasenotes/notes/fix-crashtracking-zombie-aa81dc4efa96898d.yaml new file mode 100644 index 00000000000..f0eaabaa1e3 --- /dev/null +++ b/releasenotes/notes/fix-crashtracking-zombie-aa81dc4efa96898d.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + crashtracking: Resolve issue where the crashtracker receiver may leave a zombie process behind after a crash. diff --git a/src/core/Cargo.lock b/src/core/Cargo.lock index f1d3a6c47ff..27f510e5ddc 100644 --- a/src/core/Cargo.lock +++ b/src/core/Cargo.lock @@ -34,8 +34,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "datadog-ddsketch" -version = "14.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=v14.1.0#106fe1aee81c912e3201b7d138d57dabdfed4295" +version = "14.3.1" +source = "git+https://github.com/DataDog/libdatadog?rev=v14.3.1#48240f2588665a03c2061879345566ec7e70fabf" dependencies = [ "prost", ] diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 2ff16e20e1e..3353bc8b504 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -10,7 +10,7 @@ opt-level = 3 [dependencies] pyo3 = { version = "0.21.2", features = ["extension-module"] } -datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v14.1.0" } +datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v14.3.1" } [build-dependencies] pyo3-build-config = "0.21.2" From b875079dbbe7468c86f98ce465d4c4d2c3849586 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 9 Dec 2024 16:46:22 -0500 Subject: [PATCH 269/372] ci: do not persiste git credentials in GitHub Actions (#11640) --- .github/workflows/build-and-publish-image.yml | 2 ++ .github/workflows/build_deploy.yml | 3 ++ .github/workflows/build_python_3.yml | 3 ++ .github/workflows/changelog.yml | 1 + .github/workflows/codeowners.yml | 1 + .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/django-overhead-profile.yml | 2 +- .github/workflows/encoders-profile.yml | 2 +- .github/workflows/flask-overhead-profile.yml | 2 +- .../workflows/generate-package-versions.yml | 2 ++ .github/workflows/pr-name.yml | 1 + .github/workflows/requirements-locks.yml | 1 + .github/workflows/rust-ci.yml | 2 ++ .github/workflows/set-target-milestone.yml | 5 +-- .github/workflows/system-tests.yml | 6 ++++ .github/workflows/test_frameworks.yml | 31 ++++++++++++++++++- .github/workflows/test_lib_injection.yml | 2 ++ .github/workflows/unit_tests.yml | 1 + .github/workflows/upstream-issues.yml | 2 ++ 19 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-publish-image.yml b/.github/workflows/build-and-publish-image.yml index d015d0c5c3f..2482d03a924 100644 --- a/.github/workflows/build-and-publish-image.yml +++ b/.github/workflows/build-and-publish-image.yml @@ -28,6 +28,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index 78333b890c6..77d52c757f5 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -34,6 +34,7 @@ jobs: - uses: actions/checkout@v4 # Include all history and tags with: + persist-credentials: false fetch-depth: 0 - uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions/setup-python@v5 @@ -58,6 +59,8 @@ jobs: image: python:3.9-alpine steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/download-artifact@v4 with: name: source-dist diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index f00de62e2b7..02832a008b9 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -20,6 +20,8 @@ jobs: include: ${{steps.set-matrix.outputs.include}} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-python@v5 with: python-version: '3.8' @@ -51,6 +53,7 @@ jobs: - uses: actions/checkout@v4 # Include all history and tags with: + persist-credentials: false fetch-depth: 0 - uses: actions/setup-python@v5 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 5f9ac3ec1c7..aa705d8a02f 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/checkout@v4 # Include all history and tags with: + persist-credentials: false fetch-depth: 0 # Ensure a new reno release note was added in this PR. diff --git a/.github/workflows/codeowners.yml b/.github/workflows/codeowners.yml index 3d7419846d6..3a0b5993058 100644 --- a/.github/workflows/codeowners.yml +++ b/.github/workflows/codeowners.yml @@ -12,6 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false fetch-depth: 0 - name: Get changed files id: changed-files diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e3a3edf4ecf..5af69a81073 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,6 +27,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index 6d026338ca9..8fb697daa14 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -33,6 +33,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: ddtrace - uses: actions/setup-python@v5 @@ -51,4 +52,3 @@ jobs: with: name: django-overhead-profile${{ matrix.suffix }} path: ${{ github.workspace }}/prefix/artifacts - diff --git a/.github/workflows/encoders-profile.yml b/.github/workflows/encoders-profile.yml index 887d96ead39..ed77daa6d5a 100644 --- a/.github/workflows/encoders-profile.yml +++ b/.github/workflows/encoders-profile.yml @@ -21,6 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: ddtrace - uses: actions/setup-python@v5 @@ -43,4 +44,3 @@ jobs: with: name: encoders-profile path: ${{ github.workspace }}/prefix/artifacts - diff --git a/.github/workflows/flask-overhead-profile.yml b/.github/workflows/flask-overhead-profile.yml index 4adefed18ed..8f4dce9e5c7 100644 --- a/.github/workflows/flask-overhead-profile.yml +++ b/.github/workflows/flask-overhead-profile.yml @@ -21,6 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false path: ddtrace - uses: actions/setup-python@v5 @@ -39,4 +40,3 @@ jobs: with: name: flask-overhead-profile path: ${{ github.workspace }}/prefix/artifacts - diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index 70a1a83adbf..4db524c3d04 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -16,6 +16,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Python 3.7 uses: actions/setup-python@v5 diff --git a/.github/workflows/pr-name.yml b/.github/workflows/pr-name.yml index 5a6e4bdfe80..a66c9f506e8 100644 --- a/.github/workflows/pr-name.yml +++ b/.github/workflows/pr-name.yml @@ -11,6 +11,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false fetch-depth: 0 - uses: actions/setup-node@v4 name: Install Node.js diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index a504ee43a75..69400d35dbd 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -15,6 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false fetch-depth: 0 - name: Fixup git permissions diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 668aa507f89..4bd448eaf18 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -14,6 +14,8 @@ jobs: extension: ["src/core"] steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install latest stable toolchain and rustfmt run: rustup update stable && rustup default stable && rustup component add rustfmt clippy - name: Run cargo build diff --git a/.github/workflows/set-target-milestone.yml b/.github/workflows/set-target-milestone.yml index 6370a0a7d8b..31dcb1a9938 100644 --- a/.github/workflows/set-target-milestone.yml +++ b/.github/workflows/set-target-milestone.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/checkout@v4 # Include all history and tags with: + persist-credentials: false fetch-depth: 0 - uses: actions/setup-python@v5 name: Install Python @@ -32,7 +33,7 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} script: | - const title = "${{ steps.milestones.outputs.milestone }}"; + const title = "${{ steps.milestones.outputs.milestone }}" const milestones = await github.rest.issues.listMilestones({ owner: context.repo.owner, @@ -52,6 +53,6 @@ jobs: await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: ${{ github.event.pull_request.number }}, + issue_number: context.pull_request.number, milestone: milestone.number, }); diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index e7edf051ecc..48d4196a2ee 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -18,6 +18,7 @@ jobs: - name: Checkout system tests uses: actions/checkout@v4 with: + persist-credentials: false repository: 'DataDog/system-tests' - name: Build agent @@ -62,11 +63,13 @@ jobs: - name: Checkout system tests uses: actions/checkout@v4 with: + persist-credentials: false repository: 'DataDog/system-tests' - name: Checkout dd-trace-py uses: actions/checkout@v4 with: + persist-credentials: false path: 'binaries/dd-trace-py' fetch-depth: 0 # NB this ref is necessary to keep the checkout out of detached HEAD state, which setuptools_scm requires for @@ -112,6 +115,7 @@ jobs: - name: Checkout system tests uses: actions/checkout@v4 with: + persist-credentials: false repository: 'DataDog/system-tests' - name: Build runner @@ -280,10 +284,12 @@ jobs: - name: Checkout system tests uses: actions/checkout@v4 with: + persist-credentials: false repository: 'DataDog/system-tests' - name: Checkout dd-trace-py uses: actions/checkout@v4 with: + persist-credentials: false path: 'binaries/dd-trace-py' fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 4b0124db8d6..809dee38234 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -13,7 +13,7 @@ on: workflow_dispatch: {} schedule: - cron: '0 5 * * *' - + concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -26,6 +26,8 @@ jobs: outcome: ${{ steps.run_needed.outcome }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - id: run_needed name: Check if run is needed run: | @@ -77,9 +79,11 @@ jobs: if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace + persist-credentials: false - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: bottlepy/bottle ref: 0.12.25 path: bottle @@ -146,10 +150,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: sanic-org/sanic ref: v24.6.0 path: sanic @@ -229,10 +235,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: django/django ref: 5.0.7 path: django @@ -325,10 +333,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: graphql-python/graphene # TODO: bump ref to `graphene>3.0.0`. # Unreleased CI fix: https://github.com/graphql-python/graphene/pull/1412 @@ -397,10 +407,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: tiangolo/fastapi ref: 0.92.0 path: fastapi @@ -462,10 +474,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: pallets/flask ref: 3.0.3 path: flask @@ -512,10 +526,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: encode/httpx ref: 0.22.0 path: httpx @@ -584,10 +600,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: sqlalchemy/mako ref: rel_1_3_5 path: mako @@ -654,10 +672,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: encode/starlette ref: 0.38.4 path: starlette @@ -719,10 +739,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: psf/requests ref: v2.26.0 path: requests @@ -786,10 +808,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: magicstack/asyncpg ref: v0.29.0 path: asyncpg @@ -841,10 +865,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: benoitc/gunicorn ref: 20.1.0 path: gunicorn @@ -898,10 +924,12 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false repository: unbit/uwsgi ref: 2.0.21 path: uwsgi @@ -975,6 +1003,7 @@ jobs: - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: + persist-credentials: false path: ddtrace - name: Checkout beautifulsoup if: needs.needs-run.outputs.outcome == 'success' diff --git a/.github/workflows/test_lib_injection.yml b/.github/workflows/test_lib_injection.yml index 7b9418390b8..c30988db6d1 100644 --- a/.github/workflows/test_lib_injection.yml +++ b/.github/workflows/test_lib_injection.yml @@ -32,6 +32,8 @@ jobs: - "3.13" steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install pyenv run: | export PYENV_ROOT="${HOME}/.pyenv" diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index a81755cf20f..3de25fa22b0 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -22,6 +22,7 @@ jobs: - uses: actions/checkout@v4 # Include all history and tags with: + persist-credentials: false fetch-depth: 0 - uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/.github/workflows/upstream-issues.yml b/.github/workflows/upstream-issues.yml index 1ea1f31264f..5c838bc894c 100644 --- a/.github/workflows/upstream-issues.yml +++ b/.github/workflows/upstream-issues.yml @@ -8,6 +8,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: Kyle-Verhoog/upstream-issue-notifier@v0.1.3 env: GITHUB_TOKEN: ${{ github.token }} From 80b648df812fcaf1e656eb3362731ac447c4e434 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Mon, 9 Dec 2024 19:12:42 -0500 Subject: [PATCH 270/372] fix(profiling): `asyncio` task fixes for stack v2 (#11493) This PR fixes 2 issues. 1. Associate `asyncio` tasks with spans Task rendering begins with calling `render_task_begin`, and it is then followed by call to `FrameStack::render()`. However, we only add span id association via call to `render_thread_begin()` so tasks are missing span id labels. To properly link spans to tasks, we add the label when we create a new sample for task. And while debugging this issue, discovered the following. 2. Propagate `asyncio` task names properly Echion adds a dummy frame having task name, code [here](https://github.com/taegyunkim/echion/blob/9d5bcc5867d7aefff73c837adcba4ef46eecebc6/echion/threads.h#L282). When it tries to render tasks, it [peeks at the top of the frame](https://github.com/taegyunkim/echion/blob/9d5bcc5867d7aefff73c837adcba4ef46eecebc6/echion/threads.h#L351) stack's name and uses that. However, there could be frames preceding the frame stack added by [this line](https://github.com/taegyunkim/echion/blob/9d5bcc5867d7aefff73c837adcba4ef46eecebc6/echion/threads.h#L276). Leading to using incorrect task names. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../stack_v2/include/stack_renderer.hpp | 3 + .../profiling/stack_v2/src/stack_renderer.cpp | 26 ++- ...-stack-v2-task-names-1d566a1793280aa0.yaml | 8 + tests/profiling_v2/collector/conftest.py | 8 + .../collector/test_stack_asyncio.py | 156 ++++++++++-------- 5 files changed, 125 insertions(+), 76 deletions(-) create mode 100644 releasenotes/notes/profiling-stack-v2-task-names-1d566a1793280aa0.yaml create mode 100644 tests/profiling_v2/collector/conftest.py diff --git a/ddtrace/internal/datadog/profiling/stack_v2/include/stack_renderer.hpp b/ddtrace/internal/datadog/profiling/stack_v2/include/stack_renderer.hpp index 463fdb596a6..0f8151bcafa 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/include/stack_renderer.hpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/include/stack_renderer.hpp @@ -34,6 +34,9 @@ class StackRenderer : public RendererInterface { Sample* sample = nullptr; ThreadState thread_state = {}; + // Whether task name has been pushed for the current sample. Whenever + // the sample is created, this has to be reset. + bool pushed_task_name = false; virtual void render_message(std::string_view msg) override; virtual void render_thread_begin(PyThreadState* tstate, diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp index 9a07da3aac2..5b8286b05fb 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/stack_renderer.cpp @@ -51,6 +51,8 @@ StackRenderer::render_thread_begin(PyThreadState* tstate, thread_state.cpu_time_ns = 0; // Walltime samples are guaranteed, but CPU times are not. Initialize to 0 // since we don't know if we'll get a CPU time here. + pushed_task_name = false; + // Finalize the thread information we have ddup_push_threadinfo(sample, static_cast(thread_id), static_cast(native_id), name); ddup_push_walltime(sample, thread_state.wall_time_ns, 1); @@ -64,7 +66,7 @@ StackRenderer::render_thread_begin(PyThreadState* tstate, } void -StackRenderer::render_task_begin(std::string_view name) +StackRenderer::render_task_begin(std::string_view) { static bool failed = false; if (failed) { @@ -89,9 +91,18 @@ StackRenderer::render_task_begin(std::string_view name) ddup_push_walltime(sample, thread_state.wall_time_ns, 1); ddup_push_cputime(sample, thread_state.cpu_time_ns, 1); // initialized to 0, so possibly a no-op ddup_push_monotonic_ns(sample, thread_state.now_time_ns); - } - ddup_push_task_name(sample, name); + // We also want to make sure the tid -> span_id mapping is present in the sample for the task + const std::optional active_span = + ThreadSpanLinks::get_instance().get_active_span_from_thread_id(thread_state.id); + if (active_span) { + ddup_push_span_id(sample, active_span->span_id); + ddup_push_local_root_span_id(sample, active_span->local_root_span_id); + ddup_push_trace_type(sample, std::string_view(active_span->span_type)); + } + + pushed_task_name = false; + } } void @@ -119,6 +130,15 @@ StackRenderer::render_python_frame(std::string_view name, std::string_view file, if (!utf8_check_is_valid(file.data(), file.size())) { file = invalid; } + // DEV: Echion pushes a dummy frame containing task name, and its line + // number is set to 0. + if (!pushed_task_name and line == 0) { + ddup_push_task_name(sample, name); + pushed_task_name = true; + // And return early to avoid pushing task name as a frame + return; + } + ddup_push_frame(sample, name, file, 0, line); } diff --git a/releasenotes/notes/profiling-stack-v2-task-names-1d566a1793280aa0.yaml b/releasenotes/notes/profiling-stack-v2-task-names-1d566a1793280aa0.yaml new file mode 100644 index 00000000000..c90bf4cf90e --- /dev/null +++ b/releasenotes/notes/profiling-stack-v2-task-names-1d566a1793280aa0.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixes an issue where ``asyncio`` task names are not properly propagated + when using stack v2, i.e. when ``DD_PROFILING_STACK_V2_ENABLED`` is set. + Fixes an issue where ``asyncio`` tasks are not associated with spans + when using stack v2, i.e. when ``DD_PROFILING_STACK_V2_ENABLED`` is set. + diff --git a/tests/profiling_v2/collector/conftest.py b/tests/profiling_v2/collector/conftest.py new file mode 100644 index 00000000000..b2ba1ced203 --- /dev/null +++ b/tests/profiling_v2/collector/conftest.py @@ -0,0 +1,8 @@ +import pytest + +import ddtrace + + +@pytest.fixture +def tracer(): + return ddtrace.Tracer() diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling_v2/collector/test_stack_asyncio.py index 1af4f227238..791cceb4080 100644 --- a/tests/profiling_v2/collector/test_stack_asyncio.py +++ b/tests/profiling_v2/collector/test_stack_asyncio.py @@ -1,24 +1,27 @@ -import asyncio -import glob -import os import sys -import time import pytest -from ddtrace.internal.datadog.profiling import stack_v2 -from ddtrace.profiling import _asyncio -from ddtrace.profiling import profiler -from ddtrace.settings.profiling import config -from tests.profiling.collector import _asyncio_compat -from tests.profiling.collector import pprof_utils - @pytest.mark.skipif(sys.version_info < (3, 8), reason="stack v2 is available only on 3.8+ as echion does") -def test_asyncio(monkeypatch): - pprof_output_prefix = "/tmp/test_asyncio" - monkeypatch.setattr(config.stack, "v2_enabled", True) - monkeypatch.setattr(config, "output_pprof", pprof_output_prefix) +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_stack_asyncio", + DD_PROFILING_STACK_V2_ENABLED="true", + ), +) +def test_asyncio(): + import asyncio + import os + import time + import uuid + + from ddtrace import ext + from ddtrace import tracer + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + from tests.profiling.collector import _asyncio_compat + from tests.profiling.collector import pprof_utils assert stack_v2.is_available, stack_v2.failure_msg @@ -36,79 +39,86 @@ async def hello(): await stuff() return (t1, t2) - p = profiler.Profiler() + resource = str(uuid.uuid4()) + span_type = ext.SpanTypes.WEB + + p = profiler.Profiler(tracer=tracer) + assert p._profiler._stack_v2_enabled p.start() - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - if _asyncio_compat.PY38_AND_LATER: + with tracer.trace("test_asyncio", resource=resource, span_type=span_type) as span: + span_id = span.span_id + local_root_span_id = span._local_root.span_id + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) maintask = loop.create_task(hello(), name="main") - else: - maintask = loop.create_task(hello()) - t1, t2 = loop.run_until_complete(maintask) + t1, t2 = loop.run_until_complete(maintask) p.stop() - t1_name = _asyncio._task_get_name(t1) - t2_name = _asyncio._task_get_name(t2) + t1_name = t1.get_name() + t2_name = t2.get_name() assert t1_name == "sleep 1" assert t2_name == "sleep 2" - output_filename = pprof_output_prefix + "." + str(os.getpid()) + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) profile = pprof_utils.parse_profile(output_filename) + samples_with_span_id = pprof_utils.get_samples_with_label_key(profile, "span id") + assert len(samples_with_span_id) > 0 + # get samples with task_name samples = pprof_utils.get_samples_with_label_key(profile, "task name") # The next fails if stack_v2 is not properly configured with asyncio task # tracking via ddtrace.profiling._asyncio assert len(samples) > 0 - # We'd like to check whether there exist samples with - # 1. task name label "main" - # - function name label "hello" - # - and line number is between - # 2. task name label t1_name or t2_name - # - function name label "stuff" - # And they all have thread name "MainThread" - - checked_main = False - checked_t1 = False - checked_t2 = False - - for sample in samples: - task_name_label = pprof_utils.get_label_with_key(profile.string_table, sample, "task name") - task_name = profile.string_table[task_name_label.str] - - thread_name_label = pprof_utils.get_label_with_key(profile.string_table, sample, "thread name") - thread_name = profile.string_table[thread_name_label.str] - - location_id = sample.location_id[0] - location = pprof_utils.get_location_with_id(profile, location_id) - line = location.line[0] - function = pprof_utils.get_function_with_id(profile, line.function_id) - function_name = profile.string_table[function.name] - - if task_name == "main": - assert thread_name == "MainThread" - assert function_name == "hello" - checked_main = True - elif task_name == t1_name or task_name == t2_name: - assert thread_name == "MainThread" - assert function_name == "stuff" - if task_name == t1_name: - checked_t1 = True - if task_name == t2_name: - checked_t2 = True - - assert checked_main - assert checked_t1 - assert checked_t2 - - # cleanup output file - for f in glob.glob(pprof_output_prefix + ".*"): - try: - os.remove(f) - except Exception as e: - print("Error removing file: {}".format(e)) - pass + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="main", + span_id=span_id, + local_root_span_id=local_root_span_id, + locations=[ + pprof_utils.StackLocation( + function_name="hello", filename="test_stack_asyncio.py", line_no=hello.__code__.co_firstlineno + 3 + ) + ], + ), + ) + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + span_id=span_id, + local_root_span_id=local_root_span_id, + locations=[ + pprof_utils.StackLocation( + function_name="stuff", filename="test_stack_asyncio.py", line_no=stuff.__code__.co_firstlineno + 3 + ), + ], + ), + ) + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t2_name, + span_id=span_id, + local_root_span_id=local_root_span_id, + locations=[ + pprof_utils.StackLocation( + function_name="stuff", filename="test_stack_asyncio.py", line_no=stuff.__code__.co_firstlineno + 3 + ), + ], + ), + ) From 2e2303b29ce7552a169f9cf48d09b8e37e68cbbb Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Tue, 10 Dec 2024 15:07:45 +0200 Subject: [PATCH 271/372] chore(debugger): - update debugger system tests scenarios (#11634) [DEBUG-3137] --- .github/workflows/system-tests.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 48d4196a2ee..697b0f77c48 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -237,17 +237,9 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' run: ./run.sh DEBUGGER_PROBES_STATUS - - name: Run DEBUGGER_METHOD_PROBES_SNAPSHOT + - name: Run DEBUGGER_PROBES_SNAPSHOT if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' - run: ./run.sh DEBUGGER_METHOD_PROBES_SNAPSHOT - - - name: Run DEBUGGER_LINE_PROBES_SNAPSHOT - if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' - run: ./run.sh DEBUGGER_LINE_PROBES_SNAPSHOT - - - name: Run DEBUGGER_MIX_LOG_PROBE - if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' - run: ./run.sh DEBUGGER_MIX_LOG_PROBE + run: ./run.sh DEBUGGER_PROBES_SNAPSHOT - name: Run DEBUGGER_PII_REDACTION if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'debugger-1' From c226dd1f64a03e0fc9d815783f0883f9876b7b6c Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:39:34 -0600 Subject: [PATCH 272/372] fix(setup): suppress int-ptr conversion errors for stack profiler v1 (#11651) The root issue is that * Alpine 3.21.0 was released on Dec 5 * Alpine 3.21.0 includes an update to gcc (gcc 14) * gcc 14 is more strict (yay!) about pointer<->integer conversions. However, cython does not generate code with the proper incantation to avoid compiler errors (I blame pthreads having opaque pointer types) * Subsequently, any and every python-alpine container image (even and especially patch versions) cut after Dec 5 will have the updated Alpine image, which will have the updated gcc, which will start to break during builds. This PR effectively undoes the gcc 14 behavior by making int<->ptr conversions less strict again (only for the stack.c cython-generated file which is currently throwing the error). ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../fix-profiler-int-ptr-conversion-4377fbd8724eeaec.yaml | 6 ++++++ setup.py | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-profiler-int-ptr-conversion-4377fbd8724eeaec.yaml diff --git a/releasenotes/notes/fix-profiler-int-ptr-conversion-4377fbd8724eeaec.yaml b/releasenotes/notes/fix-profiler-int-ptr-conversion-4377fbd8724eeaec.yaml new file mode 100644 index 00000000000..cadb50628fa --- /dev/null +++ b/releasenotes/notes/fix-profiler-int-ptr-conversion-4377fbd8724eeaec.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Updates setup.py to ignore int-ptr conversion warnings for the profiler stack.pyx file. + This is important because gcc 14 makes these conversions an error, alpine 3.21.0 ships with gcc 14, + and any patch version of a Python alpine image cut after December 5th, 2024, will have this issue. diff --git a/setup.py b/setup.py index 6b097d46f6b..06200930c56 100644 --- a/setup.py +++ b/setup.py @@ -612,7 +612,11 @@ def get_exts_for(name): "ddtrace.profiling.collector.stack", sources=["ddtrace/profiling/collector/stack.pyx"], language="c", - extra_compile_args=extra_compile_args, + # cython generated code errors on build in toolchains that are strict about int->ptr conversion + # OTOH, the MSVC toolchain is different. In a perfect world we'd deduce the underlying toolchain and + # emit the right flags, but as a compromise we assume Windows implies MSVC and everything else is on a + # GNU-like toolchain + extra_compile_args=extra_compile_args + (["-Wno-int-conversion"] if CURRENT_OS != "Windows" else []), ), Cython.Distutils.Extension( "ddtrace.profiling.collector._traceback", From e04c8acf72186e63998cb4c97df0d3708aaeb15d Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:19:31 -0500 Subject: [PATCH 273/372] feat(langchain): [MLOB-1972] update langchain to handle vertex and gemini llm calls (#11642) This PR updates the Langchain integration to handle LLM calls instrumented in the Vertex AI and Gemini integrations by checking for their respective provider names (`vertexai` and `google_palm`) and instrumenting the wrapper Langchain calls as workflow spans if these provider names are detected. Importantly, because of the way Langchain invokes chat generations for Vertex AI and Gemini, our integrations will not capture these inner LLM calls (in both cases, the prediction client is called directly with the input instead of using the `chat.send_message` method which we have instrumented in both cases). Therefore, we will only capture the Langchain LLM call and hence leave it as an LLM span for chat generations. ## Testing As we work on a more stable way to test our langchain integration, I opted to manually verify these changes by submitting traces to staging. Below I have included the code that I ran and the resulting trace that appeared in the product. It is expected that LLM calls have a langchain workflow span with a Gemini/Vertex AI child LLM span whereas Chat calls have only the Langchain LLM span instrumented (see above for details on why this is the case). ### Gemini LLM call ``` from langchain_google_genai import GoogleGenerativeAI llm = GoogleGenerativeAI(model="gemini-pro") print( llm.invoke( "What are some tips for improving sleep quality?" ) ) ``` ![image](https://github.com/user-attachments/assets/5d68ad43-36c1-413a-822a-eac37e650c91) ### Gemini Chat call ``` from langchain_google_genai import ChatGoogleGenerativeAI llm = ChatGoogleGenerativeAI(model="gemini-pro") resp = llm.invoke("Tell me a joke.") print(resp) ``` ![image](https://github.com/user-attachments/assets/57b9dadb-2c2b-40d5-905e-3fe261132262) ### Vertex AI LLM call ``` from langchain_google_vertexai import VertexAI model = VertexAI(model_name="gemini-pro") message = "What is the optimal temperature for sleeping?" model.invoke(message) ``` ![image](https://github.com/user-attachments/assets/74d84f58-ff71-4f60-948f-4a8494b60a17) ### Vertex AI Chat call ``` from langchain_google_vertexai import ChatVertexAI llm = ChatVertexAI( model="gemini-1.5-flash-001", temperature=0, max_tokens=None, max_retries=6, stop=None, ) messages = [ ( "system", "You are a helpful assistant that translates English to French. Translate the user sentence.", ), ("human", "I love programming."), ] ai_msg = llm.invoke(messages) print(ai_msg) ``` ![image](https://github.com/user-attachments/assets/9bb398a3-4803-4189-af11-8afe7a6add1e) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_integrations/langchain.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ddtrace/llmobs/_integrations/langchain.py b/ddtrace/llmobs/_integrations/langchain.py index c2304289c2c..2128458253d 100644 --- a/ddtrace/llmobs/_integrations/langchain.py +++ b/ddtrace/llmobs/_integrations/langchain.py @@ -44,6 +44,8 @@ ANTHROPIC_PROVIDER_NAME = "anthropic" BEDROCK_PROVIDER_NAME = "amazon_bedrock" OPENAI_PROVIDER_NAME = "openai" +VERTEXAI_PROVIDER_NAME = "vertexai" +GEMINI_PROVIDER_NAME = "google_palm" ROLE_MAPPING = { "human": "user", @@ -81,6 +83,12 @@ def _llmobs_set_tags( if model_provider: if model_provider.startswith(BEDROCK_PROVIDER_NAME): llmobs_integration = "bedrock" + # only the llm interface for Vertex AI will get instrumented + elif model_provider.startswith(VERTEXAI_PROVIDER_NAME) and operation == "llm": + llmobs_integration = "vertexai" + # only the llm interface for Gemini will get instrumented + elif model_provider.startswith(GEMINI_PROVIDER_NAME) and operation == "llm": + llmobs_integration = "google_generativeai" elif model_provider.startswith(OPENAI_PROVIDER_NAME): llmobs_integration = "openai" elif operation == "chat" and model_provider.startswith(ANTHROPIC_PROVIDER_NAME): From 8354fdffc66339db9d458996b0333c31b310d0aa Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 11 Dec 2024 09:54:16 +0000 Subject: [PATCH 274/372] refactor(debugger): use single dispatch on probes (#11596) We refactor the debugger code to use a single dispatch function solution instead of a battery of instance check branches. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 74 +++--------------- ddtrace/debugging/_encoding.py | 2 +- ddtrace/debugging/_expressions.py | 1 + ddtrace/debugging/_probe/model.py | 9 ++- ddtrace/debugging/_signal/__init__.py | 5 ++ ddtrace/debugging/_signal/collector.py | 2 +- ddtrace/debugging/_signal/log.py | 67 +++++++++++++++++ ddtrace/debugging/_signal/metric_sample.py | 15 +++- ddtrace/debugging/_signal/model.py | 87 +++++++--------------- ddtrace/debugging/_signal/snapshot.py | 15 +++- ddtrace/debugging/_signal/tracing.py | 20 ++++- tests/debugging/signal/test_collector.py | 2 +- 12 files changed, 166 insertions(+), 133 deletions(-) create mode 100644 ddtrace/debugging/_signal/log.py diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 6d0edf3a224..65b9ecfec5e 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -31,26 +31,15 @@ from ddtrace.debugging._probe.model import FunctionProbe from ddtrace.debugging._probe.model import LineLocationMixin from ddtrace.debugging._probe.model import LineProbe -from ddtrace.debugging._probe.model import LogFunctionProbe -from ddtrace.debugging._probe.model import LogLineProbe -from ddtrace.debugging._probe.model import MetricFunctionProbe -from ddtrace.debugging._probe.model import MetricLineProbe from ddtrace.debugging._probe.model import Probe -from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe -from ddtrace.debugging._probe.model import SpanDecorationLineProbe -from ddtrace.debugging._probe.model import SpanFunctionProbe from ddtrace.debugging._probe.registry import ProbeRegistry from ddtrace.debugging._probe.remoteconfig import ProbePollerEvent from ddtrace.debugging._probe.remoteconfig import ProbePollerEventType from ddtrace.debugging._probe.remoteconfig import ProbeRCAdapter from ddtrace.debugging._probe.status import ProbeStatusLogger from ddtrace.debugging._signal.collector import SignalCollector -from ddtrace.debugging._signal.metric_sample import MetricSample from ddtrace.debugging._signal.model import Signal from ddtrace.debugging._signal.model import SignalState -from ddtrace.debugging._signal.snapshot import Snapshot -from ddtrace.debugging._signal.tracing import DynamicSpan -from ddtrace.debugging._signal.tracing import SpanDecoration from ddtrace.debugging._uploader import LogsIntakeUploaderV1 from ddtrace.debugging._uploader import UploaderProduct from ddtrace.internal import compat @@ -62,7 +51,6 @@ from ddtrace.internal.module import register_post_run_module_hook from ddtrace.internal.module import unregister_post_run_module_hook from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter -from ddtrace.internal.rate_limiter import RateLimitExceeded from ddtrace.internal.remoteconfig.worker import remoteconfig_poller from ddtrace.internal.service import Service from ddtrace.internal.wrapping.context import WrappingContext @@ -190,35 +178,15 @@ def _open_signals(self) -> None: # for each probe. trace_context = self._tracer.current_trace_context() - if isinstance(probe, MetricFunctionProbe): - signal = MetricSample( - probe=probe, + try: + signal = Signal.from_probe( + probe, frame=frame, thread=thread, trace_context=trace_context, meter=self._probe_meter, ) - elif isinstance(probe, LogFunctionProbe): - signal = Snapshot( - probe=probe, - frame=frame, - thread=thread, - trace_context=trace_context, - ) - elif isinstance(probe, SpanFunctionProbe): - signal = DynamicSpan( - probe=probe, - frame=frame, - thread=thread, - trace_context=trace_context, - ) - elif isinstance(probe, SpanDecorationFunctionProbe): - signal = SpanDecoration( - probe=probe, - frame=frame, - thread=thread, - ) - else: + except TypeError: log.error("Unsupported probe type: %s", type(probe)) continue @@ -385,39 +353,19 @@ def _dd_debugger_hook(self, probe: Probe) -> None: instrumented code is running. """ try: - actual_frame = sys._getframe(1) - signal: Optional[Signal] = None - if isinstance(probe, MetricLineProbe): - signal = MetricSample( - probe=probe, - frame=actual_frame, + try: + signal = Signal.from_probe( + probe, + frame=sys._getframe(1), thread=threading.current_thread(), trace_context=self._tracer.current_trace_context(), meter=self._probe_meter, ) - elif isinstance(probe, LogLineProbe): - if probe.take_snapshot: - # TODO: Global limit evaluated before probe conditions - if self._global_rate_limiter.limit() is RateLimitExceeded: - return - - signal = Snapshot( - probe=probe, - frame=actual_frame, - thread=threading.current_thread(), - trace_context=self._tracer.current_trace_context(), - ) - elif isinstance(probe, SpanDecorationLineProbe): - signal = SpanDecoration( - probe=probe, - frame=actual_frame, - thread=threading.current_thread(), - ) - else: - log.error("Unsupported probe type: %r", type(probe)) + except TypeError: + log.error("Unsupported probe type: %r", type(probe), exc_info=True) return - signal.do_line() + signal.do_line(self._global_rate_limiter if probe.is_global_rate_limited() else None) if signal.state is SignalState.DONE: self._probe_registry.set_emitting(probe) diff --git a/ddtrace/debugging/_encoding.py b/ddtrace/debugging/_encoding.py index aa54add676a..b5f6458f4e2 100644 --- a/ddtrace/debugging/_encoding.py +++ b/ddtrace/debugging/_encoding.py @@ -15,7 +15,7 @@ from typing import Union from ddtrace.debugging._config import di_config -from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.log import LogSignal from ddtrace.debugging._signal.snapshot import Snapshot from ddtrace.internal import forksafe from ddtrace.internal._encoding import BufferFull diff --git a/ddtrace/debugging/_expressions.py b/ddtrace/debugging/_expressions.py index 50028b9c6d2..ccab7549d8f 100644 --- a/ddtrace/debugging/_expressions.py +++ b/ddtrace/debugging/_expressions.py @@ -23,6 +23,7 @@ arg_operation => {"": []} arg_op_type => filter | substring | getmember | index """ # noqa + from dataclasses import dataclass from itertools import chain import re diff --git a/ddtrace/debugging/_probe/model.py b/ddtrace/debugging/_probe/model.py index 6f989d627f4..f832484ac6d 100644 --- a/ddtrace/debugging/_probe/model.py +++ b/ddtrace/debugging/_probe/model.py @@ -85,6 +85,9 @@ def update(self, other: "Probe") -> None: for attrib in (f.name for f in fields(self) if f.compare): setattr(self, attrib, getattr(other, attrib)) + def is_global_rate_limited(self) -> bool: + return False + def __hash__(self): return hash(self.probe_id) @@ -245,12 +248,14 @@ class LogProbeMixin(AbstractProbeMixIn): @dataclass class LogLineProbe(Probe, LineLocationMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): - pass + def is_global_rate_limited(self) -> bool: + return self.take_snapshot @dataclass class LogFunctionProbe(Probe, FunctionLocationMixin, TimingMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): - pass + def is_global_rate_limited(self) -> bool: + return self.take_snapshot @dataclass diff --git a/ddtrace/debugging/_signal/__init__.py b/ddtrace/debugging/_signal/__init__.py index e69de29bb2d..6dea9afe965 100644 --- a/ddtrace/debugging/_signal/__init__.py +++ b/ddtrace/debugging/_signal/__init__.py @@ -0,0 +1,5 @@ +# DEV: Import these modules to allow registering the single dispatch functions +from ddtrace.debugging._signal.metric_sample import MetricSample # noqa +from ddtrace.debugging._signal.snapshot import Snapshot # noqa +from ddtrace.debugging._signal.tracing import DynamicSpan # noqa +from ddtrace.debugging._signal.tracing import SpanDecoration # noqa diff --git a/ddtrace/debugging/_signal/collector.py b/ddtrace/debugging/_signal/collector.py index 461e8ff1af6..57868b30485 100644 --- a/ddtrace/debugging/_signal/collector.py +++ b/ddtrace/debugging/_signal/collector.py @@ -6,7 +6,7 @@ from ddtrace.debugging._encoding import BufferedEncoder from ddtrace.debugging._metrics import metrics -from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.log import LogSignal from ddtrace.debugging._signal.model import Signal from ddtrace.debugging._signal.model import SignalState from ddtrace.internal._encoding import BufferFull diff --git a/ddtrace/debugging/_signal/log.py b/ddtrace/debugging/_signal/log.py new file mode 100644 index 00000000000..23cde73f642 --- /dev/null +++ b/ddtrace/debugging/_signal/log.py @@ -0,0 +1,67 @@ +import abc +from dataclasses import dataclass +import typing as t + +from ddtrace.debugging._probe.model import FunctionLocationMixin +from ddtrace.debugging._probe.model import LineLocationMixin +from ddtrace.debugging._signal.model import Signal + + +@dataclass +class LogSignal(Signal): + """A signal that also emits a log message. + + Some signals might require sending a log message along with the base signal + data. For example, all the collected errors from expression evaluations + (e.g. conditions) might need to be reported. + """ + + @property + @abc.abstractmethod + def message(self) -> t.Optional[str]: + """The log message to emit.""" + pass + + @abc.abstractmethod + def has_message(self) -> bool: + """Whether the signal has a log message to emit.""" + pass + + @property + def data(self) -> t.Dict[str, t.Any]: + """Extra data to include in the snapshot portion of the log message.""" + return {} + + def _probe_details(self) -> t.Dict[str, t.Any]: + probe = self.probe + if isinstance(probe, LineLocationMixin): + location = { + "file": str(probe.resolved_source_file), + "lines": [str(probe.line)], + } + elif isinstance(probe, FunctionLocationMixin): + location = { + "type": probe.module, + "method": probe.func_qname, + } + else: + return {} + + return { + "id": probe.probe_id, + "version": probe.version, + "location": location, + } + + @property + def snapshot(self) -> t.Dict[str, t.Any]: + full_data = { + "id": self.uuid, + "timestamp": int(self.timestamp * 1e3), # milliseconds + "evaluationErrors": [{"expr": e.expr, "message": e.message} for e in self.errors], + "probe": self._probe_details(), + "language": "python", + } + full_data.update(self.data) + + return full_data diff --git a/ddtrace/debugging/_signal/metric_sample.py b/ddtrace/debugging/_signal/metric_sample.py index f8bebc17d83..d92cdcec173 100644 --- a/ddtrace/debugging/_signal/metric_sample.py +++ b/ddtrace/debugging/_signal/metric_sample.py @@ -4,9 +4,12 @@ from typing import cast from ddtrace.debugging._metrics import probe_metrics +from ddtrace.debugging._probe.model import MetricFunctionProbe +from ddtrace.debugging._probe.model import MetricLineProbe from ddtrace.debugging._probe.model import MetricProbeKind from ddtrace.debugging._probe.model import MetricProbeMixin -from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.log import LogSignal +from ddtrace.debugging._signal.model import probe_to_signal from ddtrace.internal.metrics import Metrics @@ -50,3 +53,13 @@ def message(self) -> Optional[str]: def has_message(self) -> bool: return bool(self.errors) + + +@probe_to_signal.register +def _(probe: MetricFunctionProbe, frame, thread, trace_context, meter): + return MetricSample(probe=probe, frame=frame, thread=thread, trace_context=trace_context, meter=meter) + + +@probe_to_signal.register +def _(probe: MetricLineProbe, frame, thread, trace_context, meter): + return MetricSample(probe=probe, frame=frame, thread=thread, trace_context=trace_context, meter=meter) diff --git a/ddtrace/debugging/_signal/model.py b/ddtrace/debugging/_signal/model.py index a03b157adde..9c9448677c0 100644 --- a/ddtrace/debugging/_signal/model.py +++ b/ddtrace/debugging/_signal/model.py @@ -3,6 +3,8 @@ from dataclasses import dataclass from dataclasses import field from enum import Enum +from functools import singledispatch +import threading from threading import Thread import time from types import FrameType @@ -19,8 +21,6 @@ from ddtrace._trace.context import Context from ddtrace._trace.span import Span from ddtrace.debugging._expressions import DDExpressionEvaluationError -from ddtrace.debugging._probe.model import FunctionLocationMixin -from ddtrace.debugging._probe.model import LineLocationMixin from ddtrace.debugging._probe.model import Probe from ddtrace.debugging._probe.model import ProbeConditionMixin from ddtrace.debugging._probe.model import ProbeEvalTiming @@ -28,6 +28,8 @@ from ddtrace.debugging._probe.model import TimingMixin from ddtrace.debugging._safety import get_args from ddtrace.internal.compat import ExcInfoType +from ddtrace.internal.metrics import Metrics +from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded @@ -183,13 +185,17 @@ def do_exit(self, retval: Any, exc_info: ExcInfoType, duration: int) -> None: self.state = SignalState.DONE - def do_line(self) -> None: + def do_line(self, global_limiter: Optional[RateLimiter] = None) -> None: frame = self.frame scope = ChainMap(frame.f_locals, frame.f_globals) if not self._eval_condition(scope): return + if global_limiter is not None and global_limiter.limit() is RateLimitExceeded: + self.state = SignalState.SKIP_RATE + return + if self._rate_limit_exceeded(): return @@ -197,62 +203,19 @@ def do_line(self) -> None: self.state = SignalState.DONE - -@dataclass -class LogSignal(Signal): - """A signal that also emits a log message. - - Some signals might require sending a log message along with the base signal - data. For example, all the collected errors from expression evaluations - (e.g. conditions) might need to be reported. - """ - - @property - @abc.abstractmethod - def message(self) -> Optional[str]: - """The log message to emit.""" - pass - - @abc.abstractmethod - def has_message(self) -> bool: - """Whether the signal has a log message to emit.""" - pass - - @property - def data(self) -> Dict[str, Any]: - """Extra data to include in the snapshot portion of the log message.""" - return {} - - def _probe_details(self) -> Dict[str, Any]: - probe = self.probe - if isinstance(probe, LineLocationMixin): - location = { - "file": str(probe.resolved_source_file), - "lines": [str(probe.line)], - } - elif isinstance(probe, FunctionLocationMixin): - location = { - "type": probe.module, - "method": probe.func_qname, - } - else: - return {} - - return { - "id": probe.probe_id, - "version": probe.version, - "location": location, - } - - @property - def snapshot(self) -> Dict[str, Any]: - full_data = { - "id": self.uuid, - "timestamp": int(self.timestamp * 1e3), # milliseconds - "evaluationErrors": [{"expr": e.expr, "message": e.message} for e in self.errors], - "probe": self._probe_details(), - "language": "python", - } - full_data.update(self.data) - - return full_data + @staticmethod + def from_probe( + probe: Probe, frame: FrameType, thread: Thread, trace_context: Optional[Any], meter: Metrics.Meter + ) -> "Signal": + return probe_to_signal(probe, frame, thread, trace_context, meter) + + +@singledispatch +def probe_to_signal( + probe: Probe, + frame: FrameType, + thread: threading.Thread, + trace_context: Optional[Any], + meter: Metrics.Meter, +) -> Signal: + raise TypeError(f"Unsupported probe type: {type(probe)}") diff --git a/ddtrace/debugging/_signal/snapshot.py b/ddtrace/debugging/_signal/snapshot.py index 9f42921a7a3..5bb02f16659 100644 --- a/ddtrace/debugging/_signal/snapshot.py +++ b/ddtrace/debugging/_signal/snapshot.py @@ -17,6 +17,8 @@ from ddtrace.debugging._probe.model import FunctionLocationMixin from ddtrace.debugging._probe.model import LineLocationMixin from ddtrace.debugging._probe.model import LiteralTemplateSegment +from ddtrace.debugging._probe.model import LogFunctionProbe +from ddtrace.debugging._probe.model import LogLineProbe from ddtrace.debugging._probe.model import LogProbeMixin from ddtrace.debugging._probe.model import TemplateSegment from ddtrace.debugging._redaction import REDACTED_PLACEHOLDER @@ -25,8 +27,9 @@ from ddtrace.debugging._safety import get_globals from ddtrace.debugging._safety import get_locals from ddtrace.debugging._signal import utils +from ddtrace.debugging._signal.log import LogSignal from ddtrace.debugging._signal.model import EvaluationError -from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.model import probe_to_signal from ddtrace.debugging._signal.utils import serialize from ddtrace.internal.compat import ExcInfoType from ddtrace.internal.utils.time import HourGlass @@ -177,3 +180,13 @@ def data(self): "captures": captures, "duration": self.duration, } + + +@probe_to_signal.register +def _(probe: LogFunctionProbe, frame, thread, trace_context, meter): + return Snapshot(probe=probe, frame=frame, thread=thread, trace_context=trace_context) + + +@probe_to_signal.register +def _(probe: LogLineProbe, frame, thread, trace_context, meter): + return Snapshot(probe=probe, frame=frame, thread=thread, trace_context=trace_context) diff --git a/ddtrace/debugging/_signal/tracing.py b/ddtrace/debugging/_signal/tracing.py index 9d3712a963b..3c9eb3f447e 100644 --- a/ddtrace/debugging/_signal/tracing.py +++ b/ddtrace/debugging/_signal/tracing.py @@ -7,12 +7,15 @@ from ddtrace.constants import ORIGIN_KEY from ddtrace.debugging._expressions import DDExpressionEvaluationError from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe +from ddtrace.debugging._probe.model import SpanDecorationLineProbe from ddtrace.debugging._probe.model import SpanDecorationMixin from ddtrace.debugging._probe.model import SpanDecorationTargetSpan from ddtrace.debugging._probe.model import SpanFunctionProbe +from ddtrace.debugging._signal.log import LogSignal from ddtrace.debugging._signal.model import EvaluationError -from ddtrace.debugging._signal.model import LogSignal from ddtrace.debugging._signal.model import Signal +from ddtrace.debugging._signal.model import probe_to_signal from ddtrace.debugging._signal.utils import serialize from ddtrace.internal.compat import ExcInfoType from ddtrace.internal.logger import get_logger @@ -112,3 +115,18 @@ def message(self): def has_message(self) -> bool: return bool(self.errors) + + +@probe_to_signal.register +def _(probe: SpanFunctionProbe, frame, thread, trace_context, meter): + return DynamicSpan(probe=probe, frame=frame, thread=thread, trace_context=trace_context) + + +@probe_to_signal.register +def _(probe: SpanDecorationFunctionProbe, frame, thread, trace_context, meter): + return SpanDecoration(probe=probe, frame=frame, thread=thread) + + +@probe_to_signal.register +def _(probe: SpanDecorationLineProbe, frame, thread, trace_context, meter): + return SpanDecoration(probe=probe, frame=frame, thread=thread) diff --git a/tests/debugging/signal/test_collector.py b/tests/debugging/signal/test_collector.py index 2e2a77ec098..49e4f1aef2c 100644 --- a/tests/debugging/signal/test_collector.py +++ b/tests/debugging/signal/test_collector.py @@ -6,7 +6,7 @@ import mock from ddtrace.debugging._signal.collector import SignalCollector -from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.log import LogSignal from ddtrace.debugging._signal.model import SignalState from ddtrace.debugging._signal.snapshot import Snapshot from tests.debugging.utils import create_snapshot_line_probe From 3a56826f72611155d278b0abd0bd0f9072f4cd65 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 11 Dec 2024 10:16:35 +0000 Subject: [PATCH 275/372] chore(profile): more accurate code-provenance (#11577) We make the code provenance feature for the Python profiler more accurate by considering "library" anything that is not user code, in the sense of file paths that do not correspond to well-known third-party dependencies. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/profiling/exporter/pprof.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/ddtrace/profiling/exporter/pprof.pyx b/ddtrace/profiling/exporter/pprof.pyx index 121909727f1..9ed4aed5f0f 100644 --- a/ddtrace/profiling/exporter/pprof.pyx +++ b/ddtrace/profiling/exporter/pprof.pyx @@ -455,6 +455,7 @@ class _PprofConverter(object): for _ in ( (packages.filename_to_package(filename), filename) for filename, lineno, funcname in self._locations + if not packages.is_user_code(filename) ) if _[0] is not None }, _ITEMGETTER_ZERO From 264671fd51c50811faec4b82fdc56d83ed06620a Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:29:43 +0100 Subject: [PATCH 276/372] chore(asm): libddwaf 1.22.0 (#11667) Upgrade libddwaf to 1.22.0 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- releasenotes/notes/waf_1.22.0-05b1dfbaa0d47059.yaml | 4 ++++ setup.py | 2 +- ...est_processor.test_appsec_body_no_collection_snapshot.json | 2 +- ..._processor.test_appsec_cookies_no_collection_snapshot.json | 2 +- ....appsec.test_processor.test_appsec_span_tags_snapshot.json | 2 +- ..._processor.test_appsec_span_tags_snapshot_with_errors.json | 2 +- ...ango.test_django_appsec_snapshots.test_appsec_enabled.json | 2 +- ...st_django_appsec_snapshots.test_appsec_enabled_attack.json | 2 +- ...jango_appsec_snapshots.test_request_ipblock_match_403.json | 2 +- ..._appsec_snapshots.test_request_ipblock_match_403_json.json | 2 +- ...ngo_appsec_snapshots.test_request_ipblock_nomatch_200.json | 2 +- ..._flask_ipblock_match_403[flask_appsec_good_rules_env].json | 2 +- ...sk_ipblock_match_403[flask_appsec_good_rules_env]_220.json | 2 +- ...k_ipblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- ...lask_processexec_osspawn[flask_appsec_good_rules_env].json | 2 +- ..._processexec_osspawn[flask_appsec_good_rules_env]_220.json | 2 +- ...ask_processexec_ossystem[flask_appsec_good_rules_env].json | 2 +- ...processexec_ossystem[flask_appsec_good_rules_env]_220.json | 2 +- ...rocesscommunicatenoshell[flask_appsec_good_rules_env].json | 2 +- ...sscommunicatenoshell[flask_appsec_good_rules_env]_220.json | 2 +- ...bprocesscommunicateshell[flask_appsec_good_rules_env].json | 2 +- ...cesscommunicateshell[flask_appsec_good_rules_env]_220.json | 2 +- ...userblock_match_200_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_200_json[flask_appsec_good_rules_env]_220.json | 2 +- ...userblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- 27 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/waf_1.22.0-05b1dfbaa0d47059.yaml diff --git a/releasenotes/notes/waf_1.22.0-05b1dfbaa0d47059.yaml b/releasenotes/notes/waf_1.22.0-05b1dfbaa0d47059.yaml new file mode 100644 index 00000000000..def80385e74 --- /dev/null +++ b/releasenotes/notes/waf_1.22.0-05b1dfbaa0d47059.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + ASM: This upgrades libddwaf to 1.22.0 diff --git a/setup.py b/setup.py index 06200930c56..13b0cb4a4f0 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ CURRENT_OS = platform.system() -LIBDDWAF_VERSION = "1.21.0" +LIBDDWAF_VERSION = "1.22.0" # DEV: update this accordingly when src/core upgrades libdatadog dependency. # libdatadog v14.1.0 requires rust 1.76. diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index 4f093b82c80..847daf52c50 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index cbdf4ac389d..b3f0d82c699 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.origin": "appsec", "_dd.p.appsec": "1", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index 4908df4eed0..1cab0e7c25e 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "tests.appsec.appsec", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index 60c918bdc2f..28e1dd48dd3 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "tests.appsec.appsec", "_dd.p.dm": "-0", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index 344f63429a7..cd37846b283 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "1.13.3", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index 02956faa875..70652b2a242 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.13.3", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json index c5ce0d7abf9..a87fcfe4cac 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":10192376353237234254}]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json index 467d576ad85..e1a05bff80e 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":865087550764298227}]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json index 52a99d13b63..15d1b9c3565 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json index 40295cc8b37..625cad59f0a 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json index 32d8e8e4dde..c8e20851d2d 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json index a8d620726eb..aec0ebaad30 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json index 9e98ab1fa2d..ce549766664 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json index d9bf74ab521..dbd705248e8 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json index 4e3c507fb24..7503a153364 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json index a53a26279ee..168dd987711 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json index 89a294b6e8d..73c4525efbf 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json index 5265382cf59..83b9e0bae3e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json index ae473681205..4fd585c203c 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json index 2eb36d25a38..20b135b759b 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json index 800d73259aa..5fec712f209 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json index d29fdcda126..96245135611 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json index e099df45417..3166f83bff1 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index 5a0c8e301a5..3e25ed6da8e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index 806e0de6295..41661a28f5a 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.21.0", + "_dd.appsec.waf.version": "1.22.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.appsec": "1", From 2ff8083cabcde3961188d2e3dd406287c6eb0cf2 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:35:00 +0100 Subject: [PATCH 277/372] refactor(asm): update package resolution (#11650) - package resolution was not working optimally if only a sub module of a package was loaded. This PR improves that by trying to explore the parent module if the package is not found. - remove dead code using import hook as it was never loaded. Simplify code base. APPSEC-56104 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/packages.py | 40 ++++++++---- ddtrace/internal/telemetry/data.py | 14 ++-- ddtrace/internal/telemetry/modules.py | 93 ++------------------------- ddtrace/internal/telemetry/writer.py | 4 +- 4 files changed, 41 insertions(+), 110 deletions(-) diff --git a/ddtrace/internal/packages.py b/ddtrace/internal/packages.py index 8b369b9709c..ab4023d93dd 100644 --- a/ddtrace/internal/packages.py +++ b/ddtrace/internal/packages.py @@ -59,26 +59,38 @@ def get_package_distributions() -> t.Mapping[str, t.List[str]]: return _packages_distributions() -@cached(maxsize=256) -def get_module_distribution_versions(module_name: str) -> t.Dict[str, str]: +@cached(maxsize=1024) +def get_module_distribution_versions(module_name: str) -> t.Optional[t.Tuple[str, str]]: + if not module_name: + return None try: import importlib.metadata as importlib_metadata except ImportError: import importlib_metadata # type: ignore[no-redef] - try: - return { - module_name: importlib_metadata.distribution(module_name).version, - } - except importlib_metadata.PackageNotFoundError: - pass - + names: t.List[str] = [] pkgs = get_package_distributions() - names = pkgs.get(module_name) - if not names: - return {} - - return {name: get_version_for_package(name) for name in names} + while names == []: + try: + return ( + module_name, + importlib_metadata.distribution(module_name).version, + ) + except Exception: # nosec + pass + names = pkgs.get(module_name, []) + if not names: + # try to resolve the parent package + p = module_name.rfind(".") + if p > 0: + module_name = module_name[:p] + else: + break + if len(names) != 1: + # either it was not resolved due to multiple packages with the same name + # or it's a multipurpose package (like '__pycache__') + return None + return (names[0], get_version_for_package(names[0])) @cached(maxsize=256) diff --git a/ddtrace/internal/telemetry/data.py b/ddtrace/internal/telemetry/data.py index 3b73ac8b97d..a11e7f4db36 100644 --- a/ddtrace/internal/telemetry/data.py +++ b/ddtrace/internal/telemetry/data.py @@ -77,15 +77,15 @@ def update_imported_dependencies(already_imported: Dict[str, str], new_modules: if not dists: continue - for name, version in dists.items(): - if name == "ddtrace": - continue + name, version = dists + if name == "ddtrace": + continue - if name in already_imported: - continue + if name in already_imported: + continue - already_imported[name] = version - deps.append({"name": name, "version": version}) + already_imported[name] = version + deps.append({"name": name, "version": version}) return deps diff --git a/ddtrace/internal/telemetry/modules.py b/ddtrace/internal/telemetry/modules.py index 3b916fb1282..555e0b70d7e 100644 --- a/ddtrace/internal/telemetry/modules.py +++ b/ddtrace/internal/telemetry/modules.py @@ -1,92 +1,13 @@ import sys -from types import ModuleType -from typing import Any from typing import Set -from typing import Tuple -from ..compat import PYTHON_VERSION_INFO -from ..module import BaseModuleWatchdog +ALL_MODULES: Set[str] = set() # All modules that have been already imported -NEW_MODULES: Set[str] = set() # New modules that have been imported since the last check -ALL_MODULES: Set[str] = set() # All modules that have been imported -MODULE_HOOK_INSTALLED = False -# For Python >= 3.8 we can use the sys.audit event import(module, filename, sys.path, sys.meta_path, sys.path_hooks) -if PYTHON_VERSION_INFO >= (3, 8): - - def audit_hook(event: str, args: Tuple[Any, ...]): - if event != "import": - return - - global NEW_MODULES, ALL_MODULES - NEW_MODULES.add(args[0]) - ALL_MODULES.add(args[0]) - - def get_newly_imported_modules() -> Set[str]: - global MODULE_HOOK_INSTALLED, NEW_MODULES, ALL_MODULES - - # Our hook is not installed, so we are not getting notified of new imports, - # we need to track the changes manually - if not NEW_MODULES and not MODULE_HOOK_INSTALLED: - latest_modules = set(sys.modules.keys()) - NEW_MODULES = latest_modules - ALL_MODULES - ALL_MODULES = latest_modules - - new_modules = NEW_MODULES - NEW_MODULES = set() - return new_modules - - def install_import_hook(): - global MODULE_HOOK_INSTALLED, NEW_MODULES, ALL_MODULES - - # If we have not called get_newly_imported_modules yet, we can initialize to all imported modules - if not NEW_MODULES: - NEW_MODULES = set(sys.modules.keys()) - ALL_MODULES = NEW_MODULES.copy() - sys.addaudithook(audit_hook) - MODULE_HOOK_INSTALLED = True - - def uninstall_import_hook(): - # We cannot uninstall a sys audit hook - pass - -else: - - class TelemetryWriterModuleWatchdog(BaseModuleWatchdog): - _initial = True - _new_imported: Set[str] = set() - - def after_import(self, module: ModuleType) -> None: - self._new_imported.add(module.__name__) - - @classmethod - def get_new_imports(cls): - if cls._initial: - try: - # On the first call, use sys.modules to cover all imports before we started. This is not - # done on __init__ because we want to do this slow operation on the writer's periodic call - # and not on instantiation. - new_imports = list(sys.modules.keys()) - except RuntimeError: - new_imports = [] - finally: - # If there is any problem with the above we don't want to repeat this slow process, instead we just - # switch to report new dependencies on further calls - cls._initial = False - else: - new_imports = list(cls._new_imported) - - cls._new_imported.clear() - return new_imports - - def get_newly_imported_modules() -> Set[str]: - return set(TelemetryWriterModuleWatchdog.get_new_imports()) - - def install_import_hook(): - if not TelemetryWriterModuleWatchdog.is_installed(): - TelemetryWriterModuleWatchdog.install() - - def uninstall_import_hook(): - if TelemetryWriterModuleWatchdog.is_installed(): - TelemetryWriterModuleWatchdog.uninstall() +def get_newly_imported_modules() -> Set[str]: + global ALL_MODULES + latest_modules = set(sys.modules.keys()) + new_modules = latest_modules - ALL_MODULES + ALL_MODULES = latest_modules + return new_modules diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 899c77c1108..d10d0aac7f4 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -236,9 +236,8 @@ def enable(self): self.start() return True + # currently self._is_periodic is always true self.status = ServiceStatus.RUNNING - if _TelemetryConfig.DEPENDENCY_COLLECTION: - modules.install_import_hook() return True def disable(self): @@ -248,7 +247,6 @@ def disable(self): Once disabled, telemetry collection can not be re-enabled. """ self._enabled = False - modules.uninstall_import_hook() self.reset_queues() if self._is_running(): self.stop() From 3b076e624c12b76f304e6658e57c5c8b10a2082b Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 11 Dec 2024 11:58:41 +0000 Subject: [PATCH 278/372] chore: clarify wrapping context behaviour (#11576) We clarify the comments around the behaviour of wrapping context. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/wrapping/context.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ddtrace/internal/wrapping/context.py b/ddtrace/internal/wrapping/context.py index 24fd91d483e..138f542720e 100644 --- a/ddtrace/internal/wrapping/context.py +++ b/ddtrace/internal/wrapping/context.py @@ -1,3 +1,4 @@ +from abc import ABC from contextvars import ContextVar from inspect import iscoroutinefunction import sys @@ -30,8 +31,9 @@ # # Because we also want to capture the return value, our context manager extends # the Python one by implementing a __return__ method that will be called with -# the return value of the function. The __exit__ method is only called if the -# function raises an exception. +# the return value of the function. Contrary to ordinary context managers, +# though, the __exit__ method is only called if the function raises an +# exception. # # Because CPython 3.11 introduced zero-cost exceptions, we cannot nest try # blocks in the function's bytecode. In this case, we call the context manager @@ -256,7 +258,7 @@ # This is abstract and should not be used directly -class BaseWrappingContext(t.ContextManager): +class BaseWrappingContext(ABC): __priority__: int = 0 def __init__(self, f: FunctionType): From c801f288449069383b768f0ea07bb5ab8eb5c052 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 11 Dec 2024 09:22:06 -0500 Subject: [PATCH 279/372] chore(profiling): revert simplify usage of cancellation token (#11654) Reverts DataDog/dd-trace-py#11644 and fix tsan errors ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Charles de Beauchesne --- .github/workflows/system-tests.yml | 8 +++++++ .../profiling/dd_wrapper/include/uploader.hpp | 4 +--- .../profiling/dd_wrapper/src/uploader.cpp | 22 +++++++++---------- .../profiling_v2/collector/test_threading.py | 2 +- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 697b0f77c48..06604dc811c 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -153,6 +153,14 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'other' run: ./run.sh CROSSED_TRACING_LIBRARIES + - name: Run PROFILING + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'other' + run: | + cat /proc/sys/kernel/perf_event_paranoid + sudo sysctl kernel.perf_event_paranoid=1 + sudo sysctl -p + ./run.sh PROFILING + - name: Run REMOTE_CONFIG_MOCKED_BACKEND_ASM_FEATURES if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'remote-config' run: ./run.sh REMOTE_CONFIG_MOCKED_BACKEND_ASM_FEATURES diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp index 8a5394b0cb2..ed19f316fc3 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader.hpp @@ -24,9 +24,7 @@ class Uploader private: static inline std::mutex upload_lock{}; std::string errmsg; - static inline std::unique_ptr cancel{ - ddog_CancellationToken_new() - }; + static inline std::unique_ptr cancel; static inline std::atomic upload_seq{ 0 }; std::string output_filename; std::unique_ptr ddog_exporter; diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp index 1e04a45fb41..375c2e09e9e 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp @@ -109,20 +109,20 @@ Datadog::Uploader::upload(ddog_prof_Profile& profile) return false; } - // If we're here, we're about to create a new upload, so cancel any inflight ones - cancel_inflight(); - - // Create a new cancellation token. Maybe we can get away without doing this, but - // since we're recreating the uploader fresh every time anyway, we recreate one more thing. - // NB wrapping this in a unique_ptr to easily add RAII semantics; maybe should just wrap it in a - // class instead. - std::unique_ptr cancel_for_request( - ddog_CancellationToken_clone(cancel.get())); - // The upload operation sets up some global state in libdatadog (the tokio runtime), so // we ensure exclusivity here. { + // If we're here, we're about to create a new upload, so cancel any inflight ones const std::lock_guard lock_guard(upload_lock); + cancel_inflight(); + + // Create a new cancellation token. Maybe we can get away without doing this, but + // since we're recreating the uploader fresh every time anyway, we recreate one more thing. + // NB wrapping this in a unique_ptr to easily add RAII semantics; maybe should just wrap it in a + // class instead + cancel.reset(ddog_CancellationToken_new()); + std::unique_ptr cancel_for_request; + cancel_for_request.reset(ddog_CancellationToken_clone(cancel.get())); // Build and check the response object ddog_prof_Exporter_Request* req = build_res.ok; // NOLINT (cppcoreguidelines-pro-type-union-access) @@ -156,7 +156,7 @@ Datadog::Uploader::unlock() void Datadog::Uploader::cancel_inflight() { - ddog_CancellationToken_cancel(cancel.get()); + cancel.reset(); } void diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 5ca09dd8da5..abed7d0cfda 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -83,7 +83,7 @@ def test_patch(): @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="only works on linux") -@pytest.mark.subprocess(err=None) +@pytest.mark.subprocess() def test_user_threads_have_native_id(): from os import getpid from threading import Thread From 07db667aa1fe1b1c1bdffcb432dad24e5bb20b70 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:25:32 -0500 Subject: [PATCH 280/372] chore: update langchain latest version to 0.3.10 (#11519) Update langchain lockfiles and dependency package lockfiles. This performs the following updates: 1) Some langchain lockfiles use langchain `latest`. This will update langchain and dependencies. 2) Some langchain lockfiles use a pinned (non-latest) version of langchain, but require the `latest` version of another package. This will update all such packages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Quinna Halim --- .riot/requirements/11063bf.txt | 60 +++++++++--------- .riot/requirements/16c3b9f.txt | 61 +++++++++--------- .riot/requirements/1761cfc.txt | 72 +++++++++++----------- .riot/requirements/18bc2ac.txt | 107 ++++++++++++++++---------------- .riot/requirements/19f2225.txt | 62 ++++++++++--------- .riot/requirements/1ec1dbf.txt | 109 +++++++++++++++++---------------- .riot/requirements/457db9b.txt | 70 +++++++++++---------- .riot/requirements/55a4977.txt | 70 +++++++++++---------- .riot/requirements/585e779.txt | 59 +++++++++--------- .riot/requirements/a311bc2.txt | 109 +++++++++++++++++---------------- .riot/requirements/aa1fe5c.txt | 74 +++++++++++----------- .riot/requirements/cbbb0eb.txt | 107 ++++++++++++++++---------------- .riot/requirements/cf9bdda.txt | 64 +++++++++---------- .riot/requirements/d39d3de.txt | 59 +++++++++--------- 14 files changed, 556 insertions(+), 527 deletions(-) diff --git a/.riot/requirements/11063bf.txt b/.riot/requirements/11063bf.txt index 672870755a7..5f06cd4e04a 100644 --- a/.riot/requirements/11063bf.txt +++ b/.riot/requirements/11063bf.txt @@ -4,38 +4,38 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/11063bf.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==5.4.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 faiss-cpu==1.8.0 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 jsonpatch==1.33 @@ -49,8 +49,8 @@ langchain-core==0.1.52 langchain-openai==0.1.6 langchain-pinecone==0.1.0 langchain-text-splitters==0.0.2 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -58,36 +58,38 @@ numexpr==2.8.5 numpy==1.26.4 openai==1.30.3 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 pinecone-client==3.2.2 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 +tiktoken==0.8.0 tokenizers==0.19.1 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.0.7 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/16c3b9f.txt b/.riot/requirements/16c3b9f.txt index 1919ccd9e72..ac3ccd42fa3 100644 --- a/.riot/requirements/16c3b9f.txt +++ b/.riot/requirements/16c3b9f.txt @@ -4,33 +4,33 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/16c3b9f.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 backoff==2.2.1 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==4.57 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.5.14 -dnspython==2.6.1 +dnspython==2.7.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 importlib-metadata==6.11.0 iniconfig==2.0.0 jsonpatch==1.33 @@ -40,8 +40,8 @@ langchain-community==0.0.14 langchain-core==0.1.23 langchainplus-sdk==0.0.4 langsmith==0.0.87 -loguru==0.7.2 -marshmallow==3.22.0 +loguru==0.7.3 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -53,31 +53,32 @@ opentracing==2.4.0 packaging==23.2 pinecone-client==2.2.4 pluggy==1.5.0 -psutil==6.0.0 -pydantic==1.10.18 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 typing-extensions==4.12.2 typing-inspect==0.9.0 -urllib3==2.2.2 +urllib3==2.2.3 vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 diff --git a/.riot/requirements/1761cfc.txt b/.riot/requirements/1761cfc.txt index 4ccba3f60cb..6eb2c9fe558 100644 --- a/.riot/requirements/1761cfc.txt +++ b/.riot/requirements/1761cfc.txt @@ -4,40 +4,40 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1761cfc.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 @@ -49,49 +49,51 @@ langchain-core==0.2.0 langchain-openai==0.1.7 langchain-pinecone==0.1.3 langchain-text-splitters==0.2.1 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.0.7 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/18bc2ac.txt b/.riot/requirements/18bc2ac.txt index e3f60b3aad2..aaf19bf9fe2 100644 --- a/.riot/requirements/18bc2ac.txt +++ b/.riot/requirements/18bc2ac.txt @@ -4,98 +4,101 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/18bc2ac.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.7.0 -anthropic==0.34.2 -anyio==4.4.0 +anthropic==0.40.0 +anyio==4.7.0 attrs==24.2.0 -boto3==1.34.162 -botocore==1.34.162 +boto3==1.35.76 +botocore==1.35.76 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 -langchain==0.2.16 -langchain-anthropic==0.1.23 -langchain-aws==0.1.18 -langchain-cohere==0.2.4 -langchain-community==0.2.16 -langchain-core==0.2.39 -langchain-experimental==0.0.65 -langchain-openai==0.1.23 -langchain-pinecone==0.1.3 -langchain-text-splitters==0.2.4 -langsmith==0.1.117 -marshmallow==3.22.0 +langchain==0.3.10 +langchain-anthropic==0.3.0 +langchain-aws==0.2.9 +langchain-cohere==0.3.3 +langchain-community==0.3.10 +langchain-core==0.3.22 +langchain-experimental==0.3.3 +langchain-openai==0.2.11 +langchain-pinecone==0.2.0 +langchain-text-splitters==0.3.2 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 -packaging==24.1 -pandas==2.2.2 +orjson==3.10.12 +packaging==24.2 +pandas==2.2.3 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pydantic-settings==2.6.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 -pytz==2024.1 +python-dotenv==1.0.1 +pytz==2024.2 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tabulate==0.9.0 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 -tzdata==2024.1 -urllib3==2.2.2 +tzdata==2024.2 +urllib3==2.2.3 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/19f2225.txt b/.riot/requirements/19f2225.txt index dc86f7981bf..63df4f55d90 100644 --- a/.riot/requirements/19f2225.txt +++ b/.riot/requirements/19f2225.txt @@ -4,39 +4,39 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/19f2225.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==5.4.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 faiss-cpu==1.8.0 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 jmespath==1.0.1 jsonpatch==1.33 @@ -50,8 +50,8 @@ langchain-core==0.1.52 langchain-openai==0.1.6 langchain-pinecone==0.1.0 langchain-text-splitters==0.0.2 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -59,37 +59,39 @@ numexpr==2.8.5 numpy==1.26.4 openai==1.30.3 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 pinecone-client==3.2.2 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 +tiktoken==0.8.0 tokenizers==0.19.1 -tomli==2.0.1 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tomli==2.2.1 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.0.7 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/1ec1dbf.txt b/.riot/requirements/1ec1dbf.txt index 3f7fffea275..0a093e6e676 100644 --- a/.riot/requirements/1ec1dbf.txt +++ b/.riot/requirements/1ec1dbf.txt @@ -4,100 +4,103 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1ec1dbf.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.7.0 -anthropic==0.34.2 -anyio==4.4.0 +anthropic==0.40.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 -boto3==1.34.162 -botocore==1.34.162 +boto3==1.35.76 +botocore==1.35.76 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 -langchain==0.2.16 -langchain-anthropic==0.1.23 -langchain-aws==0.1.18 -langchain-cohere==0.2.4 -langchain-community==0.2.16 -langchain-core==0.2.39 -langchain-experimental==0.0.65 -langchain-openai==0.1.23 -langchain-pinecone==0.1.3 -langchain-text-splitters==0.2.4 -langsmith==0.1.117 -marshmallow==3.22.0 +langchain==0.3.10 +langchain-anthropic==0.3.0 +langchain-aws==0.2.9 +langchain-cohere==0.3.3 +langchain-community==0.3.10 +langchain-core==0.3.22 +langchain-experimental==0.3.3 +langchain-openai==0.2.11 +langchain-pinecone==0.2.0 +langchain-text-splitters==0.3.2 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 -packaging==24.1 -pandas==2.2.2 +orjson==3.10.12 +packaging==24.2 +pandas==2.2.3 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pydantic-settings==2.6.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 -pytz==2024.1 +python-dotenv==1.0.1 +pytz==2024.2 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tabulate==0.9.0 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 -tzdata==2024.1 -urllib3==2.2.2 +tzdata==2024.2 +urllib3==2.2.3 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/457db9b.txt b/.riot/requirements/457db9b.txt index 28def9f2321..12da4c338c5 100644 --- a/.riot/requirements/457db9b.txt +++ b/.riot/requirements/457db9b.txt @@ -4,39 +4,39 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/457db9b.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 @@ -48,48 +48,50 @@ langchain-core==0.2.0 langchain-openai==0.1.7 langchain-pinecone==0.1.3 langchain-text-splitters==0.2.1 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.0.7 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/55a4977.txt b/.riot/requirements/55a4977.txt index d2fdde55602..9c65a62f94a 100644 --- a/.riot/requirements/55a4977.txt +++ b/.riot/requirements/55a4977.txt @@ -4,39 +4,39 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/55a4977.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 @@ -48,48 +48,50 @@ langchain-core==0.2.0 langchain-openai==0.1.7 langchain-pinecone==0.1.3 langchain-text-splitters==0.2.1 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==2.0.7 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/585e779.txt b/.riot/requirements/585e779.txt index 2429e3e442d..3e328720bd3 100644 --- a/.riot/requirements/585e779.txt +++ b/.riot/requirements/585e779.txt @@ -4,32 +4,32 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/585e779.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 -anyio==4.4.0 +anyio==4.7.0 attrs==24.2.0 backoff==2.2.1 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==4.57 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.5.14 -dnspython==2.6.1 +dnspython==2.7.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 importlib-metadata==6.11.0 iniconfig==2.0.0 jsonpatch==1.33 @@ -39,8 +39,8 @@ langchain-community==0.0.14 langchain-core==0.1.23 langchainplus-sdk==0.0.4 langsmith==0.0.87 -loguru==0.7.2 -marshmallow==3.22.0 +loguru==0.7.3 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -52,30 +52,31 @@ opentracing==2.4.0 packaging==23.2 pinecone-client==2.2.4 pluggy==1.5.0 -psutil==6.0.0 -pydantic==1.10.18 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tqdm==4.66.5 +tiktoken==0.8.0 +tokenizers==0.21.0 +tqdm==4.67.1 typing-extensions==4.12.2 typing-inspect==0.9.0 -urllib3==2.2.2 +urllib3==2.2.3 vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 diff --git a/.riot/requirements/a311bc2.txt b/.riot/requirements/a311bc2.txt index 48ecb036fea..42f18e00ad7 100644 --- a/.riot/requirements/a311bc2.txt +++ b/.riot/requirements/a311bc2.txt @@ -4,103 +4,106 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/a311bc2.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.7.0 -anthropic==0.34.2 -anyio==4.4.0 +anthropic==0.40.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 -boto3==1.34.162 -botocore==1.34.162 +boto3==1.35.76 +botocore==1.35.76 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 -langchain==0.2.16 -langchain-anthropic==0.1.23 -langchain-aws==0.1.18 -langchain-cohere==0.2.4 -langchain-community==0.2.16 -langchain-core==0.2.39 -langchain-experimental==0.0.65 -langchain-openai==0.1.23 -langchain-pinecone==0.1.3 -langchain-text-splitters==0.2.4 -langsmith==0.1.117 -marshmallow==3.22.0 +langchain==0.3.10 +langchain-anthropic==0.3.0 +langchain-aws==0.2.9 +langchain-cohere==0.3.3 +langchain-community==0.3.10 +langchain-core==0.3.22 +langchain-experimental==0.3.3 +langchain-openai==0.2.11 +langchain-pinecone==0.2.0 +langchain-text-splitters==0.3.2 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 -packaging==24.1 -pandas==2.2.2 +orjson==3.10.12 +packaging==24.2 +pandas==2.2.3 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pydantic-settings==2.6.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 -pytz==2024.1 +python-dotenv==1.0.1 +pytz==2024.2 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tabulate==0.9.0 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 types-requests==2.31.0.6 types-urllib3==1.26.25.14 typing-extensions==4.12.2 typing-inspect==0.9.0 -tzdata==2024.1 +tzdata==2024.2 urllib3==1.26.20 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 diff --git a/.riot/requirements/aa1fe5c.txt b/.riot/requirements/aa1fe5c.txt index 7912c6eec65..bf4c4ba301f 100644 --- a/.riot/requirements/aa1fe5c.txt +++ b/.riot/requirements/aa1fe5c.txt @@ -4,41 +4,41 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/aa1fe5c.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 @@ -50,51 +50,53 @@ langchain-core==0.2.0 langchain-openai==0.1.7 langchain-pinecone==0.1.3 langchain-text-splitters==0.2.1 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 types-requests==2.31.0.6 types-urllib3==1.26.25.14 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==1.26.20 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 diff --git a/.riot/requirements/cbbb0eb.txt b/.riot/requirements/cbbb0eb.txt index 6f976af7055..91e45e45546 100644 --- a/.riot/requirements/cbbb0eb.txt +++ b/.riot/requirements/cbbb0eb.txt @@ -4,98 +4,101 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/cbbb0eb.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.7.0 -anthropic==0.34.2 -anyio==4.4.0 +anthropic==0.40.0 +anyio==4.7.0 attrs==24.2.0 -boto3==1.34.162 -botocore==1.34.162 +boto3==1.35.76 +botocore==1.35.76 certifi==2024.8.30 -charset-normalizer==3.3.2 -cohere==5.9.1 -coverage[toml]==7.6.1 +charset-normalizer==3.4.0 +cohere==5.13.3 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 iniconfig==2.0.0 -jiter==0.5.0 +jiter==0.8.0 jmespath==1.0.1 jsonpatch==1.33 jsonpointer==3.0.0 -langchain==0.2.16 -langchain-anthropic==0.1.23 -langchain-aws==0.1.18 -langchain-cohere==0.2.4 -langchain-community==0.2.16 -langchain-core==0.2.39 -langchain-experimental==0.0.65 -langchain-openai==0.1.23 -langchain-pinecone==0.1.3 -langchain-text-splitters==0.2.4 -langsmith==0.1.117 -marshmallow==3.22.0 +langchain==0.3.10 +langchain-anthropic==0.3.0 +langchain-aws==0.2.9 +langchain-cohere==0.3.3 +langchain-community==0.3.10 +langchain-core==0.3.22 +langchain-experimental==0.3.3 +langchain-openai==0.2.11 +langchain-pinecone==0.2.0 +langchain-text-splitters==0.3.2 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 numexpr==2.8.5 numpy==1.26.4 -openai==1.44.1 +openai==1.57.0 opentracing==2.4.0 -orjson==3.10.7 -packaging==24.1 -pandas==2.2.2 +orjson==3.10.12 +packaging==24.2 +pandas==2.2.3 parameterized==0.9.0 pinecone-client==5.0.1 -pinecone-plugin-inference==1.0.3 +pinecone-plugin-inference==1.1.0 pinecone-plugin-interface==0.0.7 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pydantic-settings==2.6.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 -pytz==2024.1 +python-dotenv==1.0.1 +pytz==2024.2 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tabulate==0.9.0 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tqdm==4.66.5 -types-requests==2.32.0.20240907 +tiktoken==0.8.0 +tokenizers==0.21.0 +tqdm==4.67.1 +types-requests==2.32.0.20241016 typing-extensions==4.12.2 typing-inspect==0.9.0 -tzdata==2024.1 -urllib3==2.2.2 +tzdata==2024.2 +urllib3==2.2.3 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 +wrapt==1.17.0 +yarl==1.18.3 diff --git a/.riot/requirements/cf9bdda.txt b/.riot/requirements/cf9bdda.txt index 943438066ca..d08448d036c 100644 --- a/.riot/requirements/cf9bdda.txt +++ b/.riot/requirements/cf9bdda.txt @@ -4,40 +4,40 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/cf9bdda.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 annotated-types==0.7.0 anthropic==0.26.0 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 boto3==1.34.51 botocore==1.34.51 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==5.4.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.6.7 defusedxml==0.7.1 distro==1.9.0 exceptiongroup==1.2.2 faiss-cpu==1.8.0 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 httpx-sse==0.4.0 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 iniconfig==2.0.0 jmespath==1.0.1 jsonpatch==1.33 @@ -51,8 +51,8 @@ langchain-core==0.1.52 langchain-openai==0.1.6 langchain-pinecone==0.1.0 langchain-text-splitters==0.0.2 -langsmith==0.1.117 -marshmallow==3.22.0 +langsmith==0.1.147 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -60,39 +60,41 @@ numexpr==2.8.5 numpy==1.26.4 openai==1.30.3 opentracing==2.4.0 -orjson==3.10.7 +orjson==3.10.12 packaging==23.2 pinecone-client==3.2.2 pluggy==1.5.0 -psutil==6.0.0 -pydantic==2.9.1 -pydantic-core==2.23.3 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 -s3transfer==0.10.2 +requests-toolbelt==1.0.0 +s3transfer==0.10.4 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 +tiktoken==0.8.0 tokenizers==0.19.1 -tomli==2.0.1 -tqdm==4.66.5 +tomli==2.2.1 +tqdm==4.67.1 types-requests==2.31.0.6 types-urllib3==1.26.25.14 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==1.26.20 vcrpy==5.1.0 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 diff --git a/.riot/requirements/d39d3de.txt b/.riot/requirements/d39d3de.txt index a1e88b6d11b..53ccfd3b257 100644 --- a/.riot/requirements/d39d3de.txt +++ b/.riot/requirements/d39d3de.txt @@ -4,33 +4,33 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/d39d3de.in # -ai21==2.14.1 +ai21==3.0.1 ai21-tokenizer==0.12.0 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 aiosignal==1.3.1 -anyio==4.4.0 +anyio==4.7.0 async-timeout==4.0.3 attrs==24.2.0 backoff==2.2.1 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cohere==4.57 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 dataclasses-json==0.5.14 -dnspython==2.6.1 +dnspython==2.7.0 exceptiongroup==1.2.2 fastavro==1.9.7 -filelock==3.16.0 -frozenlist==1.4.1 -fsspec==2024.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec==2024.10.0 greenlet==3.0.3 h11==0.14.0 -httpcore==1.0.5 +httpcore==1.0.7 httpx==0.27.2 -huggingface-hub==0.24.6 +huggingface-hub==0.26.5 hypothesis==6.45.0 -idna==3.8 +idna==3.10 importlib-metadata==6.11.0 iniconfig==2.0.0 jsonpatch==1.33 @@ -40,8 +40,8 @@ langchain-community==0.0.14 langchain-core==0.1.23 langchainplus-sdk==0.0.4 langsmith==0.0.87 -loguru==0.7.2 -marshmallow==3.22.0 +loguru==0.7.3 +marshmallow==3.23.1 mock==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -53,31 +53,32 @@ opentracing==2.4.0 packaging==23.2 pinecone-client==2.2.4 pluggy==1.5.0 -psutil==6.0.0 -pydantic==1.10.18 -pytest==8.3.3 +propcache==0.2.1 +psutil==6.1.0 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.23.7 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.10.1 python-dateutil==2.9.0.post0 pyyaml==6.0.2 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 sentencepiece==0.2.0 -six==1.16.0 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -sqlalchemy==2.0.34 +sqlalchemy==2.0.36 tenacity==8.5.0 -tiktoken==0.7.0 -tokenizers==0.20.0 -tomli==2.0.1 -tqdm==4.66.5 +tiktoken==0.8.0 +tokenizers==0.21.0 +tomli==2.2.1 +tqdm==4.67.1 typing-extensions==4.12.2 typing-inspect==0.9.0 urllib3==1.26.20 vcrpy==6.0.1 -wrapt==1.16.0 -yarl==1.11.1 -zipp==3.20.1 +wrapt==1.17.0 +yarl==1.18.3 +zipp==3.21.0 From fd37c0bf53b96c5e77c7c70e597f0846517c5a36 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 11 Dec 2024 14:47:14 +0000 Subject: [PATCH 281/372] refactor: encapsulate container info (#11649) We encapsulate the logic required to add container information headers to HTTP requests in the connection constructor. This elimiates the need to add the same logic across all products/services that make requests. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_uploader.py | 4 -- ddtrace/internal/http.py | 22 +++++++-- ddtrace/internal/processor/stats.py | 3 -- ddtrace/internal/remoteconfig/client.py | 4 -- ddtrace/internal/runtime/container.py | 66 ++++++++++++------------- ddtrace/internal/telemetry/writer.py | 3 -- ddtrace/internal/uds.py | 4 +- ddtrace/internal/utils/__init__.py | 3 +- ddtrace/internal/writer/writer.py | 4 -- ddtrace/profiling/exporter/http.py | 5 -- tests/tracer/runtime/test_container.py | 2 + 11 files changed, 56 insertions(+), 64 deletions(-) diff --git a/ddtrace/debugging/_uploader.py b/ddtrace/debugging/_uploader.py index c6ff84fc190..f8f1a22a9d2 100644 --- a/ddtrace/debugging/_uploader.py +++ b/ddtrace/debugging/_uploader.py @@ -12,7 +12,6 @@ from ddtrace.internal import compat from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import ForksafeAwakeablePeriodicService -from ddtrace.internal.runtime import container from ddtrace.internal.utils.http import connector from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter @@ -55,9 +54,6 @@ def __init__(self, interval: Optional[float] = None) -> None: "Accept": "text/plain", } - container.update_headers_with_container_info(self._headers, container.get_container_info()) - container.update_header_with_external_info(self._headers) - if di_config._tags_in_qs and di_config.tags: self.ENDPOINT += f"?ddtags={quote(di_config.tags)}" self._connect = connector(di_config._intake_url, timeout=di_config.upload_timeout) diff --git a/ddtrace/internal/http.py b/ddtrace/internal/http.py index c8e6c772d6b..34cee7e0793 100644 --- a/ddtrace/internal/http.py +++ b/ddtrace/internal/http.py @@ -1,10 +1,15 @@ from ddtrace.internal.compat import httplib from ddtrace.internal.compat import parse +from ddtrace.internal.runtime import container -class BasePathMixin(httplib.HTTPConnection, object): +class HTTPConnectionMixin: """ - Mixin for HTTPConnection to insert a base path to requested URLs + Mixin for HTTP(S) connections for performing internal adjustments. + + Currently this mixin performs the following adjustments: + - insert a base path to requested URLs + - update headers with container info """ _base_path = "/" # type: str @@ -12,7 +17,7 @@ class BasePathMixin(httplib.HTTPConnection, object): def putrequest(self, method, url, skip_host=False, skip_accept_encoding=False): # type: (str, str, bool, bool) -> None url = parse.urljoin(self._base_path, url) - return super(BasePathMixin, self).putrequest( + return super().putrequest( # type: ignore[misc] method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding ) @@ -23,14 +28,21 @@ def with_base_path(cls, *args, **kwargs): obj._base_path = base_path return obj + def request(self, method, url, body=None, headers={}, *, encode_chunked=False): + _headers = headers.copy() + + container.update_headers(_headers) + + return super().request(method, url, body=body, headers=_headers, encode_chunked=encode_chunked) + -class HTTPConnection(BasePathMixin, httplib.HTTPConnection): +class HTTPConnection(HTTPConnectionMixin, httplib.HTTPConnection): """ httplib.HTTPConnection wrapper to add a base path to requested URLs """ -class HTTPSConnection(BasePathMixin, httplib.HTTPSConnection): +class HTTPSConnection(HTTPConnectionMixin, httplib.HTTPSConnection): """ httplib.HTTPSConnection wrapper to add a base path to requested URLs """ diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index 1ba8e008105..f79f460582e 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -19,7 +19,6 @@ from ..hostname import get_hostname from ..logger import get_logger from ..periodic import PeriodicService -from ..runtime import container from ..writer import _human_size @@ -108,8 +107,6 @@ def __init__(self, agent_url, interval=None, timeout=1.0, retry_attempts=3): "Datadog-Meta-Tracer-Version": ddtrace.__version__, "Content-Type": "application/msgpack", } # type: Dict[str, str] - container.update_headers_with_container_info(self._headers, container.get_container_info()) - container.update_header_with_external_info(self._headers) self._hostname = "" if config._report_hostname: self._hostname = get_hostname() diff --git a/ddtrace/internal/remoteconfig/client.py b/ddtrace/internal/remoteconfig/client.py index 3f9315d0be0..6d77f220d81 100644 --- a/ddtrace/internal/remoteconfig/client.py +++ b/ddtrace/internal/remoteconfig/client.py @@ -29,7 +29,6 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.packages import is_distribution_available from ddtrace.internal.remoteconfig.constants import REMOTE_CONFIG_AGENT_ENDPOINT -from ddtrace.internal.runtime import container from ddtrace.internal.service import ServiceStatus from ddtrace.internal.utils.time import parse_isoformat @@ -240,9 +239,6 @@ def __init__(self) -> None: if additional_header_str is not None: self._headers.update(parse_tags_str(additional_header_str)) - container.update_headers_with_container_info(self._headers, container.get_container_info()) - container.update_header_with_external_info(self._headers) - tags = ddtrace.config.tags.copy() # Add git metadata tags, if available diff --git a/ddtrace/internal/runtime/container.py b/ddtrace/internal/runtime/container.py index c12a091fe21..6025981a726 100644 --- a/ddtrace/internal/runtime/container.py +++ b/ddtrace/internal/runtime/container.py @@ -1,9 +1,18 @@ import errno +from functools import lru_cache import os import re +import sys from typing import Any from typing import Dict from typing import Optional +from typing import Union + + +if sys.version_info >= (3, 8): + from typing import Literal # noqa:F401 +else: + from typing_extensions import Literal from ..constants import CONTAINER_ID_HEADER_NAME from ..constants import ENTITY_ID_HEADER_NAME @@ -130,25 +139,17 @@ def from_line(cls, line): ) -def get_container_info(pid="self"): - # type: (str) -> Optional[CGroupInfo] +@lru_cache(64) +def get_container_info(pid: Union[Literal["self"], int] = "self") -> Optional[CGroupInfo]: """ - Helper to fetch the current container id, if we are running in a container + Helper to fetch the current container id, if we are running in a container. We will parse `/proc/{pid}/cgroup` to determine our container id. - The results of calling this function are cached - - :param pid: The pid of the cgroup file to parse (default: 'self') - :type pid: str | int - :returns: The cgroup file info if found, or else None - :rtype: :class:`CGroupInfo` | None + The results of calling this function are cached. """ - - cgroup_file = "/proc/{0}/cgroup".format(pid) - try: - with open(cgroup_file, mode="r") as fp: + with open(f"/proc/{pid}/cgroup", mode="r") as fp: for line in fp: info = CGroupInfo.from_line(line) if info and (info.container_id or info.node_inode): @@ -161,27 +162,26 @@ def get_container_info(pid="self"): return None -def update_headers_with_container_info(headers: Dict, container_info: Optional[CGroupInfo]) -> None: +def update_headers(headers: Dict) -> None: """Get the container info (either the container ID or the cgroup inode) and add it to the headers.""" - if container_info is None: - return - if container_info.container_id: - headers.update( - { - CONTAINER_ID_HEADER_NAME: container_info.container_id, - ENTITY_ID_HEADER_NAME: f"ci-{container_info.container_id}", - } - ) - elif container_info.node_inode: - headers.update( - { - ENTITY_ID_HEADER_NAME: f"in-{container_info.node_inode}", - } - ) - - -def update_header_with_external_info(headers: Dict) -> None: - """Get the external environment info from the environment variable and add it to the headers.""" + container_info = get_container_info() + if container_info is not None: + if container_info.container_id: + headers.update( + { + CONTAINER_ID_HEADER_NAME: container_info.container_id, + ENTITY_ID_HEADER_NAME: f"ci-{container_info.container_id}", + } + ) + elif container_info.node_inode: + headers.update( + { + ENTITY_ID_HEADER_NAME: f"in-{container_info.node_inode}", + } + ) + + # Get the external environment info from the environment variable and add it + # to the headers external_info = os.environ.get(EXTERNAL_ENV_ENVIRONMENT_VARIABLE) if external_info: headers.update( diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index d10d0aac7f4..63e672e24dd 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -23,7 +23,6 @@ from ..compat import get_connection_response from ..encoding import JSONEncoderV2 from ..periodic import PeriodicService -from ..runtime import container from ..runtime import get_runtime_id from ..service import ServiceStatus from ..utils.formats import asbool @@ -136,8 +135,6 @@ def get_headers(self, request): headers["DD-Telemetry-Debug-Enabled"] = request["debug"] headers["DD-Telemetry-Request-Type"] = request["request_type"] headers["DD-Telemetry-API-Version"] = request["api_version"] - container.update_headers_with_container_info(headers, container.get_container_info()) - container.update_header_with_external_info(headers) return headers def get_endpoint(self, agentless: bool) -> str: diff --git a/ddtrace/internal/uds.py b/ddtrace/internal/uds.py index 5c8dbf02882..fcf4e52e916 100644 --- a/ddtrace/internal/uds.py +++ b/ddtrace/internal/uds.py @@ -2,10 +2,10 @@ from typing import Any # noqa:F401 from .compat import httplib -from .http import BasePathMixin +from .http import HTTPConnectionMixin -class UDSHTTPConnection(BasePathMixin, httplib.HTTPConnection): +class UDSHTTPConnection(HTTPConnectionMixin, httplib.HTTPConnection): """An HTTP connection established over a Unix Domain Socket.""" # It's "important" to keep the hostname and port arguments here; while there are not used by the connection diff --git a/ddtrace/internal/utils/__init__.py b/ddtrace/internal/utils/__init__.py index 294e99d1263..1d7ee493953 100644 --- a/ddtrace/internal/utils/__init__.py +++ b/ddtrace/internal/utils/__init__.py @@ -3,6 +3,7 @@ from typing import List # noqa:F401 from typing import Optional # noqa:F401 from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 class ArgumentError(Exception): @@ -13,7 +14,7 @@ class ArgumentError(Exception): def get_argument_value( - args: List[Any], + args: Union[Tuple[Any], List[Any]], kwargs: Dict[str, Any], pos: int, kw: str, diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index 59eb56bd821..01b05515984 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -31,7 +31,6 @@ from ..constants import _HTTPLIB_NO_TRACE_REQUEST from ..encoding import JSONEncoderV2 from ..logger import get_logger -from ..runtime import container from ..serverless import in_azure_function from ..serverless import in_gcp_function from ..sma import SimpleMovingAverage @@ -493,9 +492,6 @@ def __init__( } if headers: _headers.update(headers) - self._container_info = container.get_container_info() - container.update_headers_with_container_info(_headers, self._container_info) - container.update_header_with_external_info(_headers) _headers.update({"Content-Type": client.encoder.content_type}) # type: ignore[attr-defined] additional_header_str = os.environ.get("_DD_TRACE_WRITER_ADDITIONAL_HEADERS") diff --git a/ddtrace/profiling/exporter/http.py b/ddtrace/profiling/exporter/http.py index f23e12acb5a..6700e584ade 100644 --- a/ddtrace/profiling/exporter/http.py +++ b/ddtrace/profiling/exporter/http.py @@ -15,7 +15,6 @@ from ddtrace.internal import agent from ddtrace.internal import runtime from ddtrace.internal.processor.endpoint_call_counter import EndpointCallCounterProcessor -from ddtrace.internal.runtime import container from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter from ddtrace.profiling import exporter from ddtrace.profiling import recorder # noqa:F401 @@ -67,7 +66,6 @@ def __init__( self.version: typing.Optional[str] = version self.tags: typing.Dict[str, str] = tags if tags is not None else {} self.max_retry_delay: typing.Optional[float] = max_retry_delay - self._container_info: typing.Optional[container.CGroupInfo] = container.get_container_info() self.endpoint_call_counter_span_processor: typing.Optional[ EndpointCallCounterProcessor ] = endpoint_call_counter_span_processor @@ -183,9 +181,6 @@ def export( else: headers = {} - container.update_headers_with_container_info(headers, self._container_info) - container.update_header_with_external_info(headers) - profile, libs = super(PprofHTTPExporter, self).export(events, start_time_ns, end_time_ns) pprof = io.BytesIO() with gzip.GzipFile(fileobj=pprof, mode="wb") as gz: diff --git a/tests/tracer/runtime/test_container.py b/tests/tracer/runtime/test_container.py index f9becf93a41..f3c28dc90e6 100644 --- a/tests/tracer/runtime/test_container.py +++ b/tests/tracer/runtime/test_container.py @@ -307,6 +307,7 @@ def test_get_container_info(file_contents, container_id, node_inode): if file_contents is None: mock_open.side_effect = FileNotFoundError + get_container_info.cache_clear() info = get_container_info() if info is not None: @@ -344,6 +345,7 @@ def test_get_container_info_exception(mock_log, mock_from_line): # DEV: We need at least 1 line for the loop to call `CGroupInfo.from_line` with get_mock_open(read_data="\r\n") as mock_open: # Assert calling `get_container_info()` does not bubble up the exception + get_container_info.cache_clear() assert get_container_info() is None # Assert we called everything we expected From 45941708b5214e1b018fdeee7b6443257d79e2c9 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:56:01 -0600 Subject: [PATCH 282/372] chore: update formatters for native code (#11643) It's slightly annoying to make local changes to native code--I tried to consolidate these a bit and provide common overrides and logic. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../profiling/cmake/AnalysisFunc.cmake | 67 +++++----- .../profiling/crashtracker/CMakeLists.txt | 12 +- .../profiling/dd_wrapper/test/CMakeLists.txt | 3 +- .../datadog/profiling/ddup/CMakeLists.txt | 10 +- .../datadog/profiling/stack_v2/CMakeLists.txt | 5 +- .../profiling/stack_v2/test/CMakeLists.txt | 3 +- scripts/cformat.sh | 118 +++++++++++++----- scripts/cmake-format.sh | 64 +++++++--- 8 files changed, 169 insertions(+), 113 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake index b95a1205a62..2495a84ed29 100644 --- a/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/AnalysisFunc.cmake @@ -1,36 +1,29 @@ include(CheckIPOSupported) function(add_ddup_config target) - # Profiling native extensions are built with C++17, even though underlying - # repo adheres to the manylinux 2014 standard. This isn't currently a - # problem, but if it becomes one, we may have to structure the library - # differently. + # Profiling native extensions are built with C++17, even though underlying repo adheres to the manylinux 2014 + # standard. This isn't currently a problem, but if it becomes one, we may have to structure the library differently. target_compile_features(${target} PUBLIC cxx_std_17) # Common compile options - target_compile_options(${target} PRIVATE "$<$:-Os>" -ffunction-sections - -Wall - -Werror - -Wextra - -Wshadow - -Wnon-virtual-dtor - -Wold-style-cast) + target_compile_options( + ${target} + PRIVATE "$<$:-Os>" + -ffunction-sections + -Wall + -Werror + -Wextra + -Wshadow + -Wnon-virtual-dtor + -Wold-style-cast) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") # macOS-specific options - target_compile_options( - ${target} - PRIVATE "$<$:-Og;-g>" - "$<$:-Os;-g>" - ) + target_compile_options(${target} PRIVATE "$<$:-Og;-g>" "$<$:-Os;-g>") else() # Non-macOS (e.g., Linux) options - target_compile_options( - ${target} - PRIVATE "$<$:-Og;-ggdb3>" - "$<$:-Os;-ggdb3>" - -fno-semantic-interposition - ) + target_compile_options(${target} PRIVATE "$<$:-Og;-ggdb3>" + "$<$:-Os;-ggdb3>" -fno-semantic-interposition) endif() # Common link options @@ -66,22 +59,21 @@ function(add_ddup_config target) target_compile_options(${target} PRIVATE -fsanitize=${SANITIZE_OPTIONS} -fno-omit-frame-pointer) target_link_options(${target} PRIVATE -fsanitize=${SANITIZE_OPTIONS} -shared-libsan) - # Locate all directories containing relevant `.so` files - execute_process( - COMMAND bash -c "find $(${CMAKE_CXX_COMPILER} -print-file-name=) -name '*.so' -exec dirname {} \; | uniq" - OUTPUT_VARIABLE LIBSAN_LIB_PATHS - OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) + # Locate all directories containing relevant `.so` files + execute_process( + COMMAND bash -c "find $(${CMAKE_CXX_COMPILER} -print-file-name=) -name '*.so' -exec dirname {} \; | uniq" + OUTPUT_VARIABLE LIBSAN_LIB_PATHS + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY) - # Print for debugging - message(STATUS "LIBSAN_LIB_PATHS: ${LIBSAN_LIB_PATHS}") + # Print for debugging + message(STATUS "LIBSAN_LIB_PATHS: ${LIBSAN_LIB_PATHS}") - # Split the paths into a semicolon-separated list for CMake - string(REPLACE "\n" ";" LIBSAN_LIB_PATHS_LIST "${LIBSAN_LIB_PATHS}") + # Split the paths into a semicolon-separated list for CMake + string(REPLACE "\n" ";" LIBSAN_LIB_PATHS_LIST "${LIBSAN_LIB_PATHS}") - # Set RPATH to include all identified paths - set_target_properties(${target} PROPERTIES - BUILD_RPATH "${LIBSAN_LIB_PATHS_LIST}" - INSTALL_RPATH "${LIBSAN_LIB_PATHS_LIST}") + # Set RPATH to include all identified paths + set_target_properties(${target} PROPERTIES BUILD_RPATH "${LIBSAN_LIB_PATHS_LIST}" INSTALL_RPATH + "${LIBSAN_LIB_PATHS_LIST}") endif() # If DO_FANALYZER is specified and we're using gcc, then we can use -fanalyzer @@ -89,8 +81,7 @@ function(add_ddup_config target) target_compile_options(${target} PRIVATE -fanalyzer) endif() - # The main targets, ddup, crashtracker, stack_v2, and dd_wrapper are built - # as dynamic libraries, so PIC is required. And setting this is also fine - # for tests as they're loading those dynamic libraries. + # The main targets, ddup, crashtracker, stack_v2, and dd_wrapper are built as dynamic libraries, so PIC is required. + # And setting this is also fine for tests as they're loading those dynamic libraries. set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON) endfunction() diff --git a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt index a38b70fc224..2ae02df66f2 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt @@ -45,12 +45,8 @@ add_custom_command( add_library(${EXTENSION_NAME} SHARED ${CRASHTRACKER_CPP_SRC}) add_ddup_config(${EXTENSION_NAME}) -# Cython generates code that produces errors for the following, so relax compile -# options -target_compile_options( - ${EXTENSION_NAME} - PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address -) +# Cython generates code that produces errors for the following, so relax compile options +target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address) # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. @@ -61,7 +57,7 @@ set_target_properties(${EXTENSION_NAME} PROPERTIES SUFFIX "") # typical. set_target_properties(${EXTENSION_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/..") target_include_directories(${EXTENSION_NAME} PRIVATE ../dd_wrapper/include ${Datadog_INCLUDE_DIRS} - ${Python3_INCLUDE_DIRS}) + ${Python3_INCLUDE_DIRS}) if(Python3_LIBRARIES) target_link_libraries(${EXTENSION_NAME} PRIVATE dd_wrapper ${Python3_LIBRARIES}) @@ -89,7 +85,7 @@ if(NOT CRASHTRACKER_EXE_TARGET_NAME) endif() set_target_properties(crashtracker_exe PROPERTIES INSTALL_RPATH "$ORIGIN/.." OUTPUT_NAME - ${CRASHTRACKER_EXE_TARGET_NAME}) + ${CRASHTRACKER_EXE_TARGET_NAME}) # To let crashtracker find Python library at runtime set_target_properties(crashtracker_exe PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index 0be6098cd2a..e9c8f57dd60 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -31,8 +31,7 @@ function(dd_wrapper_add_test name) set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/..") if(LIB_INSTALL_DIR) - install(TARGETS ${name} - RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) + install(TARGETS ${name} RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) endif() endfunction() diff --git a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt index 8aeb0f1c23a..fe92fac3952 100644 --- a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt @@ -50,12 +50,8 @@ add_custom_command( add_library(${EXTENSION_NAME} SHARED ${DDUP_CPP_SRC}) add_ddup_config(${EXTENSION_NAME}) -# Cython generates code that produces errors for the following, so relax compile -# options -target_compile_options( - ${EXTENSION_NAME} - PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address -) +# Cython generates code that produces errors for the following, so relax compile options +target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address) # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. @@ -66,7 +62,7 @@ set_target_properties(${EXTENSION_NAME} PROPERTIES SUFFIX "") # typical. set_target_properties(${EXTENSION_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/..") target_include_directories(${EXTENSION_NAME} PRIVATE ../dd_wrapper/include ${Datadog_INCLUDE_DIRS} - ${Python3_INCLUDE_DIRS}) + ${Python3_INCLUDE_DIRS}) if(Python3_LIBRARIES) target_link_libraries(${EXTENSION_NAME} PRIVATE dd_wrapper ${Python3_LIBRARIES}) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt index 50c8d056c79..69788494920 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt @@ -74,9 +74,9 @@ target_compile_definitions(${EXTENSION_NAME} PRIVATE UNWIND_NATIVE_DISABLE) # warning(push, 0 then pop for the same effect. target_include_directories( ${EXTENSION_NAME} PRIVATE .. # include dd_wrapper from the root in order to make its paths transparent in the code - include) + include) target_include_directories(${EXTENSION_NAME} SYSTEM PRIVATE ${echion_SOURCE_DIR} ${Python3_INCLUDE_DIRS} - include/vendored include/util) + include/vendored include/util) # Echion sources need to be given the current platform if(WIN32) @@ -115,4 +115,3 @@ if(BUILD_TESTING) enable_testing() add_subdirectory(test) endif() - diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt index 926f9b28af7..05176f2c803 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -29,8 +29,7 @@ function(dd_wrapper_add_test name) endif() if(LIB_INSTALL_DIR) - install(TARGETS ${name} - RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) + install(TARGETS ${name} RUNTIME DESTINATION ${LIB_INSTALL_DIR}/../test) endif() endfunction() diff --git a/scripts/cformat.sh b/scripts/cformat.sh index b7d4fe46a2f..b12119e639a 100755 --- a/scripts/cformat.sh +++ b/scripts/cformat.sh @@ -1,43 +1,95 @@ -#!/bin/bash +#!/usr/bin/env bash set -e -# For more modern versions: -# clang-format --dry-run -Werror file.c -# would be enough… - -clean () -{ - rm -f "$CFORMAT_TMP" +clean() { + rm -f "$CFORMAT_TMP" 2>/dev/null || true } trap clean EXIT -if [[ "$1" == "update" ]] -then - THIS_PATH="$(realpath "$0")" - THIS_DIR="$(dirname $(dirname "$THIS_PATH"))" - # Find .c, , .h, .cpp, and .hpp files, excluding specified directories - find "$THIS_DIR" -type f \( -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.hpp' \) \ - | grep -v '.eggs/' \ - | grep -v 'dd-trace-py/build/' \ - | grep -v '_taint_tracking/CMakeFiles' \ - | grep -v '_taint_tracking/_deps/' \ - | grep -v '.riot/' \ - | grep -v 'ddtrace/vendor/' \ - | grep -v '_taint_tracking/_vendor/' \ - | grep -v 'ddtrace/appsec/_iast/_taint_tracking/cmake-build-debug/' \ - | grep -v '^ddtrace/appsec/_iast/_taint_tracking/_vendor/' \ - | while IFS= read -r file; do - clang-format -i $file - echo "Formatting $file" +# Exclude patterns applied to file list +exclude_patterns() { + local patterns=( + '^ddtrace/vendor/' + '^ddtrace/appsec/_iast/_taint_tracking/_vendor/' + '.eggs/' + 'dd-trace-py/build/' + '_taint_tracking/CMakeFiles' + '_taint_tracking/_deps/' + '.riot/' + '_taint_tracking/_vendor/' + 'ddtrace/appsec/_iast/_taint_tracking/cmake-build-debug/' + ) + + # Join all patterns with '|' + local joined="$(IFS='|'; echo "${patterns[*]}")" + + grep -vE "${joined}" +} + +# Function to enumerate files depending on mode +enumerate_files() { + local extensions=( + '*.c' + '*.h' + '*.cpp' + '*.hpp' + ) + + if [[ "$ENUM_ALL" == "true" ]]; then + local find_conditions=() + for ext in "${extensions[@]}"; do + find_conditions+=("-o" "-name" "$ext") + done + unset 'find_conditions[-1]' + find "$BASE_DIR" -type f \( "${find_conditions[@]}" \) + else + git ls-files "${extensions[@]}" + fi +} + +# Script defaults +UPDATE_MODE=false +ENUM_ALL=false +BASE_DIR=$(dirname "$(realpath "$0")") +CLANG_FORMAT=clang-format + +# NB: consumes the arguments +while (( "$#" )); do + case "$1" in + --fix|-fix|fix) + UPDATE_MODE="true" + ;; + --all|-all|all) + ENUM_ALL="true" + ;; + *) + ;; + esac done + +# Environment variable overrides +[[ -n "${CFORMAT_FIX:-}" ]] && UPDATE_MODE=true +[[ -n "${CFORMAT_ALL:-}" ]] && ENUM_ALL=true +[[ -n "${CFORMAT_BIN:-}" ]] && CLANG_FORMAT="$CLANG_FORMAT_BIN" + +if [[ "$UPDATE_MODE" == "true" ]]; then + # Update mode: Format files in-place + enumerate_files \ + | exclude_patterns \ + | while IFS= read -r file; do + ${CLANG_FORMAT} -i "$file" + echo "Formatting $file" + done else - git ls-files '*.c' '*.h' '*.cpp' '*.hpp' | grep -v '^ddtrace/vendor/' | grep -v '^ddtrace/appsec/_iast/_taint_tracking/_vendor/' | while read filename - do - CFORMAT_TMP=`mktemp` - clang-format "$filename" > "$CFORMAT_TMP" - diff -u "$filename" "$CFORMAT_TMP" - rm -f "$CFORMAT_TMP" - done + # Check mode: Compare formatted output to existing files + enumerate_files \ + | exclude_patterns \ + | while IFS= read -r filename; do + CFORMAT_TMP=$(mktemp) + ${CLANG_FORMAT} "$filename" > "$CFORMAT_TMP" + diff -u "$filename" "$CFORMAT_TMP" || true + rm -f "$CFORMAT_TMP" + done fi diff --git a/scripts/cmake-format.sh b/scripts/cmake-format.sh index 401f4d07fd5..0c272e4eb32 100755 --- a/scripts/cmake-format.sh +++ b/scripts/cmake-format.sh @@ -1,26 +1,50 @@ -#!/bin/bash +#!/usr/bin/env bash set -e -# Navigate to the root of the repository, which is one level up from the directory containing this script. -SCRIPT_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" -cd "$SCRIPT_ROOT/.." - -# Set some options for cmake-format -# If --update is passed as first arg, or if CMAKE_FORMAT_FIX_ALL is set, update the files in place +# Script defaults +UPDATE_MODE=false +ENUM_ALL=false CMD_OPT="--check" -if [[ "${1:-}" == "--update" || -n "${CMAKE_FORMAT_FIX_ALL:-}" ]]; then - CMD_OPT="--in-place" -fi +BASE_DIR=$(dirname "$(realpath "$0")") +CMAKE_FORMAT="cmake-format" + +# NB: consumes the arguments +while (( "$#" )); do + case "$1" in + --fix|-fix|fix) + UPDATE_MODE=true + ;; + --all|-all|all) + ENUM_ALL=true + ;; + *) + ;; + esac + shift +done + +# Environment variable overrides +[[ -n "${CMAKE_FORMAT_FIX:-}" || "$UPDATE_MODE" == "true" ]] && CMD_OPT="--in-place" +[[ -n "${CMAKE_FORMAT_ALL:-}" ]] && ENUM_ALL=true +[[ -n "${CMAKE_FORMAT_BIN:-}" ]] && CMAKE_FORMAT="$CMAKE_FORMAT_BIN" -# If the CMAKE_FORMAT_CHECK_ALL environnment variable is truthy, check all files -# else, just check the files that have been modified -if [[ -n "${CMAKE_FORMAT_CHECK_ALL:-}" ]]; then - FILES=$(find . -name '*.cmake' -o -name 'CMakeLists.txt' | grep -vE '^./build/' | grep -vE '_vendor/') -else - FILES=$(git diff --name-only HEAD | grep -E '\.cmake$|CMakeLists.txt' | grep -vE '^build/' | grep -vE '_vendor/' || true) -fi +# Enumerate files function +enumerate_files() { + if [[ "$ENUM_ALL" == true ]]; then + find $BASE_DIR \( -name '*.cmake' -o -name 'CMakeLists.txt' \) + else + git ls-files \ + | grep -E '\.cmake$|CMakeLists.txt' || true + fi +} + +# Enumerate and filter files +FILES=$(enumerate_files | grep -vE '^(\./)?build/' | grep -vE '_vendor/') # Run cmake-format on all files -for file in $FILES; do - cmake-format -c "scripts/.cmake-format" $CMD_OPT "$file" -done +# Use a process substitution to allow iterating safely +while IFS= read -r file; do + [[ -n "$file" ]] || continue + ${CMAKE_FORMAT} -c "scripts/.cmake-format" $CMD_OPT "$file" +done <<< "$FILES" + From aec08be632843f03339692e94b30380c9c6d73b5 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 11 Dec 2024 15:13:56 +0000 Subject: [PATCH 283/372] chore(symdb): upload compressed symbol payloads (#11404) We add support for compressed symbol database payloads. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/symbol_db/symbols.py | 14 ++++++++++---- ddtrace/internal/utils/http.py | 9 +++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ddtrace/internal/symbol_db/symbols.py b/ddtrace/internal/symbol_db/symbols.py index e252e22c0bf..228ed53f53d 100644 --- a/ddtrace/internal/symbol_db/symbols.py +++ b/ddtrace/internal/symbol_db/symbols.py @@ -3,6 +3,7 @@ from dataclasses import field import dis from enum import Enum +import gzip from http.client import HTTPResponse from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS @@ -484,13 +485,18 @@ def upload(self) -> HTTPResponse: ), FormData( name="file", - filename="symdb_export.json", - data=json.dumps(self.to_json()), - content_type="json", + filename=f"symbols_{os.getpid()}.json.gz", + data="[symbols_placeholder]", + content_type="gzip", ), ] ) + # DEV: The as_bytes method ends up writing the data line by line, which + # breaks the final payload. We add a placeholder instead and manually + # replace it with the compressed JSON. + body = body.replace(b"[symbols_placeholder]", gzip.compress(json.dumps(self.to_json()).encode("utf-8"))) + with connector(get_trace_url(), timeout=5.0)() as conn: log.debug("[PID %d] SymDB: Uploading symbols payload", os.getpid()) conn.request("POST", "/symdb/v1/input", body, headers) @@ -527,7 +533,7 @@ def is_module_included(module: ModuleType) -> bool: class SymbolDatabaseUploader(BaseModuleWatchdog): - __scope_limit__ = 100 + __scope_limit__ = 400 def __init__(self) -> None: super().__init__() diff --git a/ddtrace/internal/utils/http.py b/ddtrace/internal/utils/http.py index cba605a1527..7e85ce01356 100644 --- a/ddtrace/internal/utils/http.py +++ b/ddtrace/internal/utils/http.py @@ -1,5 +1,6 @@ from contextlib import contextmanager from dataclasses import dataclass +from email.encoders import encode_noop from json import loads import logging import os @@ -418,7 +419,7 @@ def parse_message(msg): class FormData: name: str filename: str - data: str + data: Union[str, bytes] content_type: str @@ -431,12 +432,12 @@ def multipart(parts: List[FormData]) -> Tuple[bytes, dict]: del msg["MIME-Version"] for part in parts: - app = MIMEApplication(part.data, part.content_type, lambda _: _) + app = MIMEApplication(part.data, part.content_type, encode_noop) app.add_header("Content-Disposition", "form-data", name=part.name, filename=part.filename) del app["MIME-Version"] msg.attach(app) # Split headers and body - headers, _, body = msg.as_string(policy=HTTP).partition("\r\n\r\n") + headers, _, body = msg.as_bytes(policy=HTTP).partition(b"\r\n\r\n") - return body.encode("utf-8"), dict(_.split(": ") for _ in headers.splitlines()) + return body, dict(_.split(": ") for _ in headers.decode().splitlines()) From 25e6215dd94af40397cfa15a2d39e3e61a1692c5 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 11 Dec 2024 10:48:09 -0500 Subject: [PATCH 284/372] chore: fix cmake format (#11672) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/test/CMakeLists.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index e9c8f57dd60..b80ace74968 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -21,12 +21,11 @@ function(dd_wrapper_add_test name) target_link_libraries(${name} PRIVATE gmock gtest_main dd_wrapper nlohmann_json::nlohmann_json) add_ddup_config(${name}) - gtest_discover_tests(${name} - PROPERTIES - # We start new threads after fork(), and we want to continue - # running the tests after that instead of dying. - ENVIRONMENT "TSAN_OPTIONS=die_after_fork=0:suppressions=${CMAKE_CURRENT_SOURCE_DIR}/TSan.supp" - ) + gtest_discover_tests( + ${name} + PROPERTIES # We start new threads after fork(), and we want to continue running the tests after that instead of + # dying. + ENVIRONMENT "TSAN_OPTIONS=die_after_fork=0:suppressions=${CMAKE_CURRENT_SOURCE_DIR}/TSan.supp") set_target_properties(${name} PROPERTIES INSTALL_RPATH "$ORIGIN/..") From 490e6580cb702f37a0e8483c48008929c29d93ad Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:42:04 -0600 Subject: [PATCH 285/372] chore(profiling): tweak lock collector impl for clarity (#11661) This is just a hygiene PR to clarify the implementation of the lock collector. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/profiling/collector/_lock.py | 10 +++++----- ddtrace/profiling/collector/asyncio.py | 4 ++-- ddtrace/profiling/collector/threading.py | 4 ++-- tests/profiling/collector/test_threading.py | 4 ++-- tests/profiling_v2/collector/test_threading.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ddtrace/profiling/collector/_lock.py b/ddtrace/profiling/collector/_lock.py index f2d1289d73b..4ee0e692fac 100644 --- a/ddtrace/profiling/collector/_lock.py +++ b/ddtrace/profiling/collector/_lock.py @@ -338,12 +338,12 @@ def __init__( self.export_libdd_enabled = False @abc.abstractmethod - def _get_original(self): + def _get_patch_target(self): # type: (...) -> typing.Any pass @abc.abstractmethod - def _set_original( + def _set_patch_target( self, value, # type: typing.Any ): @@ -367,7 +367,7 @@ def patch(self): """Patch the module for tracking lock allocation.""" # We only patch the lock from the `threading` module. # Nobody should use locks from `_thread`; if they do so, then it's deliberate and we don't profile. - self.original = self._get_original() + self._original = self._get_patch_target() def _allocate_lock(wrapped, instance, args, kwargs): lock = wrapped(*args, **kwargs) @@ -381,9 +381,9 @@ def _allocate_lock(wrapped, instance, args, kwargs): self.export_libdd_enabled, ) - self._set_original(FunctionWrapper(self.original, _allocate_lock)) + self._set_patch_target(FunctionWrapper(self._original, _allocate_lock)) def unpatch(self): # type: (...) -> None """Unpatch the threading module for tracking lock allocation.""" - self._set_original(self.original) + self._set_patch_target(self._original) diff --git a/ddtrace/profiling/collector/asyncio.py b/ddtrace/profiling/collector/asyncio.py index af57db3d3ad..fe5b63ab8ce 100644 --- a/ddtrace/profiling/collector/asyncio.py +++ b/ddtrace/profiling/collector/asyncio.py @@ -36,11 +36,11 @@ def _start_service(self): self._asyncio_module = asyncio return super(AsyncioLockCollector, self)._start_service() - def _get_original(self): + def _get_patch_target(self): # type: (...) -> typing.Any return self._asyncio_module.Lock - def _set_original( + def _set_patch_target( self, value # type: typing.Any ): # type: (...) -> None diff --git a/ddtrace/profiling/collector/threading.py b/ddtrace/profiling/collector/threading.py index 86daee689f6..3700c145312 100644 --- a/ddtrace/profiling/collector/threading.py +++ b/ddtrace/profiling/collector/threading.py @@ -32,11 +32,11 @@ class ThreadingLockCollector(_lock.LockCollector): PROFILED_LOCK_CLASS = _ProfiledThreadingLock - def _get_original(self): + def _get_patch_target(self): # type: (...) -> typing.Any return threading.Lock - def _set_original( + def _set_patch_target( self, value # type: typing.Any ): # type: (...) -> None diff --git a/tests/profiling/collector/test_threading.py b/tests/profiling/collector/test_threading.py index c6b646a3f98..ae7e7204a68 100644 --- a/tests/profiling/collector/test_threading.py +++ b/tests/profiling/collector/test_threading.py @@ -56,12 +56,12 @@ def test_patch(): lock = threading.Lock collector = collector_threading.ThreadingLockCollector(r) collector.start() - assert lock == collector.original + assert lock == collector._original # wrapt makes this true assert lock == threading.Lock collector.stop() assert lock == threading.Lock - assert collector.original == threading.Lock + assert collector._original == threading.Lock def test_lock_acquire_events(): diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index abed7d0cfda..bb55e67522f 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -74,12 +74,12 @@ def test_patch(): lock = threading.Lock collector = collector_threading.ThreadingLockCollector(None) collector.start() - assert lock == collector.original + assert lock == collector._original # wrapt makes this true assert lock == threading.Lock collector.stop() assert lock == threading.Lock - assert collector.original == threading.Lock + assert collector._original == threading.Lock @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="only works on linux") From 9ba734f98c89c50dd4d58e7a2521e4964e1ba670 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:07:46 -0500 Subject: [PATCH 286/372] fix(langchain): pydantic output parser tagging does not throw (#11652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MLOB-1973 ## What does this PR do? Fixes #11638 Adds some extra checking around the tagging of JSON-like output parsers in streamed cases. These kinds of output parsers concatenate their output for us, so we do not need to append a bunch of chunks together. It was previously thought that the only type was `JsonOutputParser`, which could be `json.dumps`'d as a string tag. However, the `PydanticOutputParser` inherits from `JsonOutputParser`, but cannot be JSON dumped. Thus, we just stringify it instead. To avoid this behavior of throwing in the future, I've added a `try`/`except` to the `json.dumps`. I've special-cased `PydanticOuputParser` as to not generalize it as an expensive exception to `json.dumps`. These are the only two JSON-type output parsers I've seen, but should more be introduced, we'll log our incompatability and just attempt to `str` it instead. ## Testing For a script like: ```python from typing import List from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field class Person(BaseModel): """Information about a person.""" name: str = Field(..., description="The name of the person") height_in_meters: float = Field( ..., description="The height of the person expressed in meters." ) class People(BaseModel): """Identifying information about all people in a text.""" people: List[Person] # Set up a parser parser = PydanticOutputParser(pydantic_object=People) # Prompt prompt = ChatPromptTemplate.from_messages( [ ( "system", "Answer the user query. Wrap the output in `json` tags\n{format_instructions}", ), ("human", "{query}"), ] ).partial(format_instructions=parser.get_format_instructions()) query = "Anna is 23 years old and she is 6 feet tall" llm = ChatOpenAI() chain = prompt | llm | parser for event in chain.stream({ "query": query }): print(event) ``` The output tagging is as follows on APM spans: Screenshot 2024-12-10 at 12 04 57 PM and LLMObs spans: Screenshot 2024-12-10 at 12 05 17 PM without throwing errors. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/langchain/patch.py | 16 ++++++++++------ ...pydantic-output-parsers-19bc162212ec051e.yaml | 4 ++++ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/langchain-pydantic-output-parsers-19bc162212ec051e.yaml diff --git a/ddtrace/contrib/internal/langchain/patch.py b/ddtrace/contrib/internal/langchain/patch.py index ce72e1affff..fa2332d70f2 100644 --- a/ddtrace/contrib/internal/langchain/patch.py +++ b/ddtrace/contrib/internal/langchain/patch.py @@ -1,4 +1,3 @@ -import json import os import sys from typing import Any @@ -954,17 +953,22 @@ def _on_span_started(span: Span): span.set_tag_str("langchain.request.inputs.%d.%s" % (idx, k), integration.trunc(str(v))) def _on_span_finished(span: Span, streamed_chunks): + maybe_parser = instance.steps[-1] if instance.steps else None if ( streamed_chunks and langchain_core - and isinstance(instance.steps[-1], langchain_core.output_parsers.JsonOutputParser) + and isinstance(maybe_parser, langchain_core.output_parsers.JsonOutputParser) ): - # it's possible that the chain has a json output parser - # this will have already concatenated the chunks into a json object + # it's possible that the chain has a json output parser type + # this will have already concatenated the chunks into an object - # it's also possible the json output parser isn't the last step, + # it's also possible the this parser type isn't the last step, # but one of the last steps, in which case we won't act on it here - content = json.dumps(streamed_chunks[-1]) + result = streamed_chunks[-1] + if maybe_parser.__class__.__name__ == "JsonOutputParser": + content = safe_json(result) + else: + content = str(result) else: # best effort to join chunks together content = "".join([str(chunk) for chunk in streamed_chunks]) diff --git a/releasenotes/notes/langchain-pydantic-output-parsers-19bc162212ec051e.yaml b/releasenotes/notes/langchain-pydantic-output-parsers-19bc162212ec051e.yaml new file mode 100644 index 00000000000..687e465723a --- /dev/null +++ b/releasenotes/notes/langchain-pydantic-output-parsers-19bc162212ec051e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + langchain: resolves a JSON decoding issue resulting from tagging streamed outputs from chains ending with a PydanticOutputParser. From 385d8e013858ce061f0d775951fb399e56215508 Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:13:07 -0500 Subject: [PATCH 287/372] fix: kafka consumer parenting logic (#11653) Fixes bug where Kafka consumer was creating unparented spans when consuming a message with no context present. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- ddtrace/contrib/internal/kafka/patch.py | 2 +- ...a-consumer-parenting-29acfd08e05d2350.yaml | 5 ++++ tests/contrib/kafka/test_kafka.py | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-kafka-consumer-parenting-29acfd08e05d2350.yaml diff --git a/ddtrace/contrib/internal/kafka/patch.py b/ddtrace/contrib/internal/kafka/patch.py index 225c0f82877..b8e8fce007d 100644 --- a/ddtrace/contrib/internal/kafka/patch.py +++ b/ddtrace/contrib/internal/kafka/patch.py @@ -247,7 +247,7 @@ def _instrument_message(messages, pin, start_ns, instance, err): name=schematize_messaging_operation(kafkax.CONSUME, provider="kafka", direction=SpanDirection.PROCESSING), service=trace_utils.ext_service(pin, config.kafka), span_type=SpanTypes.WORKER, - child_of=ctx if ctx is not None else pin.tracer.context_provider.active(), + child_of=ctx if ctx is not None and ctx.trace_id is not None else pin.tracer.context_provider.active(), activate=True, ) as span: # reset span start time to before function call diff --git a/releasenotes/notes/fix-kafka-consumer-parenting-29acfd08e05d2350.yaml b/releasenotes/notes/fix-kafka-consumer-parenting-29acfd08e05d2350.yaml new file mode 100644 index 00000000000..df8cdcfe986 --- /dev/null +++ b/releasenotes/notes/fix-kafka-consumer-parenting-29acfd08e05d2350.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + kafka: Fixes an issue with Kafka consumer spans not using the active trace context when distributed + tracing was enabled and no valid distributed context found was found within a consumed message. diff --git a/tests/contrib/kafka/test_kafka.py b/tests/contrib/kafka/test_kafka.py index 9bcf4ffc538..d49f85f26b2 100644 --- a/tests/contrib/kafka/test_kafka.py +++ b/tests/contrib/kafka/test_kafka.py @@ -885,6 +885,35 @@ def test_context_header_injection_works_no_client_added_headers(kafka_topic, pro assert propagation_asserted is True +def test_consumer_uses_active_context_when_no_valid_distributed_context_exists( + kafka_topic, producer, consumer, dummy_tracer +): + # use a random int in this string to prevent reading a message produced by a previous test run + test_string = "producer does not inject context test " + str(random.randint(0, 1000)) + test_key = "producer does not inject context test " + str(random.randint(0, 1000)) + PAYLOAD = bytes(test_string, encoding="utf-8") + + producer.produce(kafka_topic, PAYLOAD, key=test_key) + producer.flush() + + Pin.override(consumer, tracer=dummy_tracer) + + with dummy_tracer.trace("kafka consumer parent span") as parent_span: + with override_config("kafka", dict(distributed_tracing_enabled=True)): + message = None + while message is None or str(message.value()) != str(PAYLOAD): + message = consumer.poll() + + traces = dummy_tracer.pop_traces() + consume_span = traces[len(traces) - 1][-1] + + # assert consumer_span parent is our custom span + assert consume_span.name == "kafka.consume" + assert consume_span.parent_id == parent_span.span_id + + Pin.override(consumer, tracer=None) + + def test_span_has_dsm_payload_hash(dummy_tracer, consumer, producer, kafka_topic): Pin.override(producer, tracer=dummy_tracer) Pin.override(consumer, tracer=dummy_tracer) From c2a3fa4d70ad049ca3c2a944d8a1c5b0099944d8 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 12 Dec 2024 15:37:05 +0100 Subject: [PATCH 288/372] chore(ci): iast packages tests error (#11673) New error in CircleCI but it works locally. It looks the same error of this old PR: https://github.com/DataDog/dd-trace-py/pull/11199 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/iast_packages/test_packages.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index c738eb231b9..86aad989007 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -495,8 +495,7 @@ def uninstall(self, python_cmd): "d8b5635eb590e078a608e083351288a0", "", import_module_to_validate="multipart.multipart", - # This test is failing in CircleCI because, for some reason, instead of installing version - # 0.0.5, it’s installing the latest version + # This test is failing in CircleCI with the latest version test_import=False, test_propagation=True, ), @@ -573,6 +572,8 @@ def uninstall(self, python_cmd): "Parsed TOML data: {'key': 'value'}", "", import_module_to_validate="tomli._parser", + # This test is failing in CircleCI with the latest version + test_import=False, test_propagation=True, ), PackageForTesting( From 965af0821d82088ac4570042a2a445c1e682b164 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:02:34 +0100 Subject: [PATCH 289/372] chore(ci_visibility): respect EFD threshold and fix is_new tagging (#11691) Fixes a pair of issues: - properly respects the absolute number of new tests threshold when checking session faultiness - stops tagging tests as new when a session is considered faulty No release note because EFD support is in beta and unreleased ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/ci_visibility/api/_session.py | 17 +- ddtrace/internal/ci_visibility/api/_test.py | 6 +- .../api/fake_runner_efd_faulty_session.py | 19 +- tests/ci_visibility/test_efd.py | 142 +- ...st_manual_api_fake_efd_faulty_session.json | 2392 ++++++++++++++--- 5 files changed, 2213 insertions(+), 363 deletions(-) diff --git a/ddtrace/internal/ci_visibility/api/_session.py b/ddtrace/internal/ci_visibility/api/_session.py index b6407cc86be..5267a345c0a 100644 --- a/ddtrace/internal/ci_visibility/api/_session.py +++ b/ddtrace/internal/ci_visibility/api/_session.py @@ -119,8 +119,8 @@ def set_efd_abort_reason(self, abort_reason: str): self._efd_abort_reason = abort_reason def efd_is_faulty_session(self): - """A session is considered "EFD faulty" if percentage of tests considered new is greater than the given - threshold + """A session is considered "EFD faulty" if the percentage of tests considered new is greater than the + given threshold, and the total number of news tests exceeds the threshold. NOTE: this behavior is cached on the assumption that this method will only be called once """ @@ -130,16 +130,19 @@ def efd_is_faulty_session(self): if self._session_settings.efd_settings.enabled is False: return False - total_tests = 0 - new_tests = 0 + total_tests_count = 0 + new_tests_count = 0 for _module in self._children.values(): for _suite in _module._children.values(): for _test in _suite._children.values(): - total_tests += 1 + total_tests_count += 1 if _test.is_new(): - new_tests += 1 + new_tests_count += 1 - new_tests_pct = 100 * (new_tests / total_tests) + if new_tests_count <= self._session_settings.efd_settings.faulty_session_threshold: + return False + + new_tests_pct = 100 * (new_tests_count / total_tests_count) self._efd_is_faulty_session = new_tests_pct > self._session_settings.efd_settings.faulty_session_threshold diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index 73dc6397b63..c0eb615cd03 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -101,8 +101,10 @@ def _set_efd_tags(self) -> None: self.set_tag(TEST_EFD_ABORT_REASON, self._efd_abort_reason) # NOTE: The is_new tag is currently only being set in the context of EFD (since that is the only context in - # which unique tests are fetched). - if self.is_new(): + # which unique tests are fetched). Additionally, if a session is considered faulty, we do not want to tag the + # test as new. + session = self.get_session() + if self.is_new() and session is not None and not session.efd_is_faulty_session(): self.set_tag(TEST_IS_NEW, self._is_new) def _set_atr_tags(self) -> None: diff --git a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py index ea841888de6..4937464e74f 100644 --- a/tests/ci_visibility/api/fake_runner_efd_faulty_session.py +++ b/tests/ci_visibility/api/fake_runner_efd_faulty_session.py @@ -1,6 +1,5 @@ -"""Fake test runner where all too many tests are new, so the session is faulty and no retries are done - -Incorporates setting and deleting tags, as well. +"""Fake test runner where too many tests are new, so the session is faulty and no retries are done +. Starts session before discovery (simulating pytest behavior) Comment lines in the test start/finish lines are there for visual distinction. @@ -90,18 +89,8 @@ def run_tests(): m2_s1_id = ext_api.TestSuiteId(m2_id, "m2_s1") api.InternalTestSuite.discover(m2_s1_id) - # M2_S1 tests (mostly exist to keep under faulty session threshold) - m2_s1_test_ids = [ - api.InternalTestId(m2_s1_id, "m2_s1_t1"), - api.InternalTestId(m2_s1_id, "m2_s1_t2"), - api.InternalTestId(m2_s1_id, "m2_s1_t3"), - api.InternalTestId(m2_s1_id, "m2_s1_t4"), - api.InternalTestId(m2_s1_id, "m2_s1_t5"), - api.InternalTestId(m2_s1_id, "m2_s1_t6"), - api.InternalTestId(m2_s1_id, "m2_s1_t7"), - api.InternalTestId(m2_s1_id, "m2_s1_t8"), - api.InternalTestId(m2_s1_id, "m2_s1_t9"), - ] + # M2_S1 tests + m2_s1_test_ids = [api.InternalTestId(m2_s1_id, f"m2_s1_t{i}") for i in range(35)] for test_id in m2_s1_test_ids: api.InternalTest.discover(test_id) diff --git a/tests/ci_visibility/test_efd.py b/tests/ci_visibility/test_efd.py index c623e5db329..0e2de603c6a 100644 --- a/tests/ci_visibility/test_efd.py +++ b/tests/ci_visibility/test_efd.py @@ -66,7 +66,7 @@ def test_efd_max_retries(self, efd_settings, efd_test_duration_s, expected_max_r mock_session = mock.Mock() mock_session.efd_is_faulty_session.return_value = False - with mock.patch.multiple(efd_test, get_session=lambda *args: mock_session): + with mock.patch.object(TestVisibilityTest, "get_session", lambda *args: mock_session): efd_test.start() # Overwrite the test duration efd_test._span.start_ns -= efd_test_duration_s * 1e9 @@ -156,7 +156,7 @@ def test_efd_final_status(self, test_result, retry_results: t.Iterable[TestStatu ) mock_session = mock.Mock() mock_session.efd_is_faulty_session.return_value = False - with mock.patch.multiple(efd_test, get_session=lambda *args: mock_session): + with mock.patch.object(TestVisibilityTest, "get_session", lambda *args: mock_session): efd_test.start() efd_test.finish_test(test_result) expected_num_retry = 0 @@ -177,13 +177,87 @@ def test_efd_does_not_retry_if_disabled(self): efd_test.finish_test(TestStatus.FAIL) assert efd_test.efd_should_retry() is False - @pytest.mark.parametrize("faulty_session_threshold,expected_faulty", ((None, False), (10, True), (40, False))) - def test_efd_session_faulty(self, faulty_session_threshold, expected_faulty): - """Tests that the number of new tests in a session is correctly used to determine if a session is faulty + @pytest.mark.parametrize( + "faulty_session_threshold,expected_faulty", ((None, True), (10, True), (40, True), (50, False)) + ) + def test_efd_session_faulty_percentage(self, faulty_session_threshold, expected_faulty): + """Tests that the number of new tests in a session is correctly used to determine if a session is faulty based + on the percentage of new tests (as opposed to the absolute number). + + In order to test the percentages fully without hitting the absolute number of new tests threshold, we generate + a large number of both known and new tests. + + There are a total of 100 known and 100 new tests, so 50% are new + """ + + if faulty_session_threshold is not None: + efd_settings = EarlyFlakeDetectionSettings(True, faulty_session_threshold=faulty_session_threshold) + else: + efd_settings = EarlyFlakeDetectionSettings(True) + + ssettings = self._get_session_settings(efd_settings=efd_settings) + test_session = TestVisibilitySession(session_settings=ssettings) + + # Modules 1 and 2 each have one suite with 30 known tests and 20 new tests. + m1_id = TestModuleId("module_1") + m1 = TestVisibilityModule(m1_id.name, session_settings=ssettings) + test_session.add_child(m1_id, m1) + m1_s1_id = TestSuiteId(m1_id, "m1_s1") + m1_s1 = TestVisibilitySuite(m1_s1_id.name, session_settings=ssettings) + m1.add_child(m1_s1_id, m1_s1) + + # Known tests: + for i in range(50): + test_name = f"m1_s1_known_t{i}" + m1_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=False), + ) + + for i in range(50): + test_name = f"m1_s1_new_t{i}" + m1_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=True), + ) + + m2_id = TestModuleId("module_2") + m2 = TestVisibilityModule(m2_id.name, session_settings=ssettings) + test_session.add_child(m2_id, m2) + m2_s1_id = TestSuiteId(m2_id, "suite_1") + m2_s1 = TestVisibilitySuite(m2_s1_id.name, session_settings=ssettings) + m2.add_child(m2_s1_id, m2_s1) + + # Known tests: + for i in range(50): + test_name = f"m2_s1_known_t{i}" + m2_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=False), + ) + + for i in range(50): + test_name = f"m2_s1_new_t{i}" + m2_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=True), + ) + + assert test_session.efd_is_faulty_session() == expected_faulty + + @pytest.mark.parametrize( + "faulty_session_threshold,expected_faulty", ((None, True), (10, True), (40, False), (50, False)) + ) + def test_efd_session_faulty_absolute(self, faulty_session_threshold, expected_faulty): + """Tests that the number of new tests in a session is correctly used to determine if a session is faulty based + on the absolute number of new tests. For the purpose of this test, the test structure is hardcoded. Whether or not tests are properly marked as new, etc., should be tested elsewhere. + + There are a total of 10 known tests and 40 new tests, so 80% of tests are new. """ + if faulty_session_threshold is not None: efd_settings = EarlyFlakeDetectionSettings(True, faulty_session_threshold=faulty_session_threshold) else: @@ -192,25 +266,28 @@ def test_efd_session_faulty(self, faulty_session_threshold, expected_faulty): ssettings = self._get_session_settings(efd_settings=efd_settings) test_session = TestVisibilitySession(session_settings=ssettings) - # Module + # Modules 1 and 2 each have one suite with 5 known tests and 20 new tests. m1_id = TestModuleId("module_1") m1 = TestVisibilityModule(m1_id.name, session_settings=ssettings) test_session.add_child(m1_id, m1) m1_s1_id = TestSuiteId(m1_id, "m1_s1") m1_s1 = TestVisibilitySuite(m1_s1_id.name, session_settings=ssettings) m1.add_child(m1_s1_id, m1_s1) - m1_s1_t1_id = InternalTestId(m1_s1_id, name="m1_s1_t1") - m1_s1.add_child(m1_s1_t1_id, TestVisibilityTest(m1_s1_t1_id.name, session_settings=ssettings, is_new=True)) - m1_s1_t2_id = InternalTestId(m1_s1_id, name="m1_s1_t2") - m1_s1.add_child(m1_s1_t2_id, TestVisibilityTest(m1_s1_t2_id.name, session_settings=ssettings, is_new=False)) - m1_s1_t3_id = InternalTestId(m1_s1_id, name="m1_s1_t3") - m1_s1.add_child(m1_s1_t3_id, TestVisibilityTest(m1_s1_t3_id.name, session_settings=ssettings, is_new=False)) - - m1_s2_id = TestSuiteId(m1_id, "suite_2") - m1_s2 = TestVisibilitySuite(m1_s2_id.name, session_settings=ssettings) - m1.add_child(m1_s2_id, m1_s2) - m1_s2_t1_id = InternalTestId(m1_s2_id, name="m1_s2_t1") - m1_s2.add_child(m1_s2_t1_id, TestVisibilityTest(m1_s2_t1_id.name, session_settings=ssettings, is_new=True)) + + # Known tests: + for i in range(5): + test_name = f"m1_s1_known_t{i}" + m1_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=False), + ) + + for i in range(20): + test_name = f"m1_s1_new_t{i}" + m1_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=True), + ) m2_id = TestModuleId("module_2") m2 = TestVisibilityModule(m2_id.name, session_settings=ssettings) @@ -219,20 +296,19 @@ def test_efd_session_faulty(self, faulty_session_threshold, expected_faulty): m2_s1 = TestVisibilitySuite(m2_s1_id.name, session_settings=ssettings) m2.add_child(m2_s1_id, m2_s1) - m2_s1_t1_id = InternalTestId(m2_s1_id, name="m2_s1_t1") - m2_s1.add_child(m2_s1_t1_id, TestVisibilityTest(m2_s1_t1_id.name, session_settings=ssettings, is_new=False)) - m2_s1_t2_id = InternalTestId(m2_s1_id, name="m2_s1_t2") - m2_s1.add_child(m2_s1_t2_id, TestVisibilityTest(m2_s1_t2_id.name, session_settings=ssettings, is_new=False)) - m2_s1_t3_id = InternalTestId(m2_s1_id, name="m2_s1_t3") - m2_s1.add_child(m2_s1_t3_id, TestVisibilityTest(m2_s1_t3_id.name, session_settings=ssettings, is_new=False)) - - # A test with parameters is never considered new: - m2_s1_t4_id = InternalTestId(m2_s1_id, name="m2_s1_t4", parameters='{"hello": "world"}') - m2_s1.add_child( - m2_s1_t4_id, - TestVisibilityTest( - m2_s1_t4_id.name, session_settings=ssettings, is_new=True, parameters=m2_s1_t4_id.parameters - ), - ) + # Known tests: + for i in range(5): + test_name = f"m2_s1_known_t{i}" + m2_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=False), + ) + + for i in range(20): + test_name = f"m2_s1_new_t{i}" + m2_s1.add_child( + InternalTestId(m1_s1_id, name=test_name), + TestVisibilityTest(test_name, session_settings=ssettings, is_new=True), + ) assert test_session.efd_is_faulty_session() == expected_faulty diff --git a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json index db01cee9ccf..cd01c1a80b8 100644 --- a/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json +++ b/tests/snapshots/test_api_fake_runners.test_manual_api_fake_efd_faulty_session.json @@ -13,7 +13,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -24,7 +24,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -35,18 +35,17 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t1", @@ -54,9 +53,9 @@ "test.status": "pass", "test.suite": "m1_s1", "test.type": "test", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test" }, "metrics": { @@ -64,12 +63,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495, + "process_id": 97018, "test.source.end": 2, "test.source.start": 1 }, - "duration": 81875, - "start": 1733391714548202334 + "duration": 80708, + "start": 1734010946928117799 }], [ { @@ -86,7 +85,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -97,7 +96,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -108,27 +107,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m1", "test.module_path": "", "test.name": "m1_s1_t2", "test.status": "pass", "test.suite": "m1_s1", "test.type": "test", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test" }, "metrics": { @@ -136,10 +134,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 66625, - "start": 1733391714563523250 + "duration": 76500, + "start": 1734010946944395590 }], [ { @@ -156,7 +154,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -167,7 +165,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -178,13 +176,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.codeowners": "[\"@romain\", \"@romain2\"]", "test.command": "manual_efd_faulty_session", @@ -197,9 +195,9 @@ "test.status": "skip", "test.suite": "m1_s1", "test.type": "test", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test" }, "metrics": { @@ -207,12 +205,12 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495, + "process_id": 97018, "test.source.end": 12, "test.source.start": 4 }, - "duration": 71208, - "start": 1733391714563681417 + "duration": 67583, + "start": 1734010946944573757 }], [ { @@ -229,7 +227,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -240,7 +238,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -251,13 +249,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -269,9 +267,9 @@ "test.status": "skip", "test.suite": "m1_s1", "test.type": "test", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test" }, "metrics": { @@ -279,10 +277,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 36083, - "start": 1733391714563825542 + "duration": 29584, + "start": 1734010946944705715 }], [ { @@ -299,7 +297,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -310,7 +308,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -321,13 +319,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -339,9 +337,9 @@ "test.status": "pass", "test.suite": "m1_s1", "test.type": "test", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test" }, "metrics": { @@ -349,10 +347,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 35125, - "start": 1733391714563932792 + "duration": 29125, + "start": 1734010946944795424 }], [ { @@ -369,7 +367,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -380,7 +378,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -391,13 +389,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -407,7 +405,7 @@ "test.framework_version": "1.0.0", "test.itr.tests_skipping.enabled": "false", "test.status": "pass", - "test_session_id": "15705414272000062156", + "test_session_id": "18323133602450366815", "type": "test_session_end" }, "metrics": { @@ -415,10 +413,10 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 18241542, - "start": 1733391714547670500 + "duration": 20594916, + "start": 1734010946927447174 }, { "name": "test_visibility.module", @@ -434,7 +432,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -445,7 +443,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -456,12 +454,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -471,8 +469,8 @@ "test.module": "m1", "test.module_path": "", "test.status": "pass", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", "type": "test_module_end" }, "metrics": { @@ -480,8 +478,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 16004833, - "start": 1733391714548140542 + "duration": 17017958, + "start": 1734010946927985882 }, { "name": "test_visibility.suite", @@ -497,7 +495,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -508,7 +506,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -519,12 +517,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -533,9 +531,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m1_s1", - "test_module_id": "7932912067234031810", - "test_session_id": "15705414272000062156", - "test_suite_id": "845846750843749324", + "test_module_id": "8540260240647316329", + "test_session_id": "18323133602450366815", + "test_suite_id": "12178664196634984526", "type": "test_suite_end" }, "metrics": { @@ -543,8 +541,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 15893417, - "start": 1733391714548171417 + "duration": 16846042, + "start": 1734010946928084757 }, { "name": "test_visibility.module", @@ -560,7 +558,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -571,7 +569,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -582,12 +580,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.code_coverage.enabled": "false", "test.command": "manual_efd_faulty_session", @@ -597,8 +595,8 @@ "test.module": "m2", "test.module_path": "", "test.status": "pass", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", "type": "test_module_end" }, "metrics": { @@ -606,8 +604,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 1614125, - "start": 1733391714564197709 + "duration": 2905833, + "start": 1734010946945047549 }, { "name": "test_visibility.suite", @@ -623,7 +621,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -634,7 +632,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -645,12 +643,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -659,9 +657,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s1", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test_suite_end" }, "metrics": { @@ -669,8 +667,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 900042, - "start": 1733391714564224375 + "duration": 2378667, + "start": 1734010946945070465 }, { "name": "test_visibility.suite", @@ -686,7 +684,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -697,7 +695,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -708,12 +706,12 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -722,9 +720,9 @@ "test.module_path": "", "test.status": "pass", "test.suite": "m2_s2", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test_suite_end" }, "metrics": { @@ -732,14 +730,14 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1 }, - "duration": 559708, - "start": 1733391714565176084 + "duration": 381542, + "start": 1734010946947489007 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t1", + "resource": "m2_s1_t0", "trace_id": 6, "span_id": 1, "parent_id": 0, @@ -750,7 +748,1525 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t0", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 32417, + "start": 1734010946945087257 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t1", + "trace_id": 7, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t1", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 26458, + "start": 1734010946945178674 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t2", + "trace_id": 8, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t2", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 29084, + "start": 1734010946945258340 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t3", + "trace_id": 9, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t3", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 23375, + "start": 1734010946945334507 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t4", + "trace_id": 10, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t4", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 22625, + "start": 1734010946945402174 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t5", + "trace_id": 11, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t5", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21500, + "start": 1734010946945466590 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t6", + "trace_id": 12, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t6", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21000, + "start": 1734010946945528757 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t7", + "trace_id": 13, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t7", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20083, + "start": 1734010946945590132 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t8", + "trace_id": 14, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t8", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21208, + "start": 1734010946945651882 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t9", + "trace_id": 15, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t9", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21625, + "start": 1734010946945713840 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t10", + "trace_id": 16, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t10", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19750, + "start": 1734010946945776090 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t11", + "trace_id": 17, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t11", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21583, + "start": 1734010946945838549 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t12", + "trace_id": 18, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t12", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19625, + "start": 1734010946945909299 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t13", + "trace_id": 19, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t13", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 21792, + "start": 1734010946945968590 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t14", + "trace_id": 20, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t14", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20542, + "start": 1734010946946032757 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t15", + "trace_id": 21, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t15", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19166, + "start": 1734010946946092674 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t16", + "trace_id": 22, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t16", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20375, + "start": 1734010946946156174 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t17", + "trace_id": 23, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t17", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20750, + "start": 1734010946946217424 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t18", + "trace_id": 24, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t18", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19833, + "start": 1734010946946278132 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t19", + "trace_id": 25, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t19", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 23375, + "start": 1734010946946356882 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t20", + "trace_id": 26, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t20", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19959, + "start": 1734010946946424090 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t21", + "trace_id": 27, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t21", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 22208, + "start": 1734010946946483924 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t22", + "trace_id": 28, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -761,7 +2277,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -772,27 +2288,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t1", + "test.name": "m2_s1_t22", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -800,17 +2315,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 34542, - "start": 1733391714564244375 + "duration": 19834, + "start": 1734010946946554215 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t2", - "trace_id": 7, + "resource": "m2_s1_t23", + "trace_id": 29, "span_id": 1, "parent_id": 0, "type": "test", @@ -820,7 +2335,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -831,7 +2346,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -842,27 +2357,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t2", + "test.name": "m2_s1_t23", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -870,17 +2384,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 33542, - "start": 1733391714564346250 + "duration": 20125, + "start": 1734010946946613590 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t3", - "trace_id": 8, + "resource": "m2_s1_t24", + "trace_id": 30, "span_id": 1, "parent_id": 0, "type": "test", @@ -890,7 +2404,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -901,7 +2415,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -912,27 +2426,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t3", + "test.name": "m2_s1_t24", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -940,17 +2453,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 33250, - "start": 1733391714564456125 + "duration": 20334, + "start": 1734010946946676715 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t4", - "trace_id": 9, + "resource": "m2_s1_t25", + "trace_id": 31, "span_id": 1, "parent_id": 0, "type": "test", @@ -960,7 +2473,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -971,7 +2484,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -982,27 +2495,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t4", + "test.name": "m2_s1_t25", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1010,17 +2522,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 32250, - "start": 1733391714564552959 + "duration": 19000, + "start": 1734010946946737507 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t5", - "trace_id": 10, + "resource": "m2_s1_t26", + "trace_id": 32, "span_id": 1, "parent_id": 0, "type": "test", @@ -1030,7 +2542,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1041,7 +2553,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1052,27 +2564,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t5", + "test.name": "m2_s1_t26", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1080,17 +2591,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 30417, - "start": 1733391714564645042 + "duration": 20083, + "start": 1734010946946795382 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t6", - "trace_id": 11, + "resource": "m2_s1_t27", + "trace_id": 33, "span_id": 1, "parent_id": 0, "type": "test", @@ -1100,7 +2611,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1111,7 +2622,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1122,27 +2633,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t6", + "test.name": "m2_s1_t27", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1150,17 +2660,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 31583, - "start": 1733391714564735834 + "duration": 33667, + "start": 1734010946946856965 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t7", - "trace_id": 12, + "resource": "m2_s1_t28", + "trace_id": 34, "span_id": 1, "parent_id": 0, "type": "test", @@ -1170,7 +2680,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1181,7 +2691,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1192,27 +2702,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t7", + "test.name": "m2_s1_t28", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1220,17 +2729,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 31666, - "start": 1733391714564827459 + "duration": 22958, + "start": 1734010946946935049 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t8", - "trace_id": 13, + "resource": "m2_s1_t29", + "trace_id": 35, "span_id": 1, "parent_id": 0, "type": "test", @@ -1240,7 +2749,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1251,7 +2760,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1262,27 +2771,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t8", + "test.name": "m2_s1_t29", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1290,17 +2798,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 31750, - "start": 1733391714564918667 + "duration": 20875, + "start": 1734010946947001382 }], [ { "name": "test_visibility.test", "service": "test-test", - "resource": "m2_s1_t9", - "trace_id": 14, + "resource": "m2_s1_t30", + "trace_id": 36, "span_id": 1, "parent_id": 0, "type": "test", @@ -1310,7 +2818,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1321,7 +2829,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1332,27 +2840,302 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", - "test.name": "m2_s1_t9", + "test.name": "m2_s1_t30", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 19292, + "start": 1734010946947063965 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t31", + "trace_id": 37, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t31", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20750, + "start": 1734010946947123215 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t32", + "trace_id": 38, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t32", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 22792, + "start": 1734010946947226215 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t33", + "trace_id": 39, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t33", + "test.status": "pass", + "test.suite": "m2_s1", + "test.type": "test", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 97018 + }, + "duration": 20334, + "start": 1734010946947289840 + }], +[ + { + "name": "test_visibility.test", + "service": "test-test", + "resource": "m2_s1_t34", + "trace_id": 40, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "test_manual_api_fake_efd_faulty_session0", + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "675ae84200000000", + "ci.job.name": "test-job", + "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", + "ci.node.labels": "[\"runner:test-test-test-test\"]", + "ci.node.name": "14727097", + "ci.pipeline.id": "43949931", + "ci.pipeline.name": "Test/test-test/test-project-path", + "ci.pipeline.number": "14726", + "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", + "ci.provider.name": "gitlab", + "ci.stage.name": "test-stage", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", + "component": "dd_manual_test_fw", + "git.branch": "test.brancn/test_name", + "git.commit.author.date": "2024-09-10T10:11:13+01:00", + "git.commit.author.email": "First.Last@testtest.com", + "git.commit.author.name": "TestFirst TestLast", + "git.commit.message": "test commit message", + "git.commit.sha": "c165eb71ef833b752783b5268f21521fd16f812a", + "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", + "git.tag": "v1.0.0", + "language": "python", + "library_version": "2.18.0.dev111+g4ca600932", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "6.6.12-linuxkit", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", + "runtime.name": "CPython", + "runtime.version": "3.11.9", + "span.kind": "test", + "test.command": "manual_efd_faulty_session", + "test.framework": "dd_manual_test_fw", + "test.framework_version": "1.0.0", + "test.module": "m2", + "test.module_path": "", + "test.name": "m2_s1_t34", "test.status": "pass", "test.suite": "m2_s1", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "3960011827465066479", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "13136832476816543943", "type": "test" }, "metrics": { @@ -1360,17 +3143,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 32291, - "start": 1733391714565010709 + "duration": 19833, + "start": 1734010946947350132 }], [ { "name": "test_visibility.test", "service": "test-test", "resource": "m2_s2_t1", - "trace_id": 15, + "trace_id": 41, "span_id": 1, "parent_id": 0, "type": "test", @@ -1380,7 +3163,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1391,7 +3174,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1402,13 +3185,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1420,9 +3203,9 @@ "test.status": "skip", "test.suite": "m2_s2", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test" }, "metrics": { @@ -1430,19 +3213,19 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495, + "process_id": 97018, "test.source.end": 2, "test.source.start": 1 }, - "duration": 44250, - "start": 1733391714565197084 + "duration": 39917, + "start": 1734010946947506132 }], [ { "name": "test_visibility.test", "service": "test-test", "resource": "m2_s2_t2", - "trace_id": 16, + "trace_id": 42, "span_id": 1, "parent_id": 0, "type": "test", @@ -1452,7 +3235,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1463,7 +3246,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1474,27 +3257,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s2_t2", "test.status": "pass", "test.suite": "m2_s2", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test" }, "metrics": { @@ -1502,17 +3284,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 32666, - "start": 1733391714565305209 + "duration": 21333, + "start": 1734010946947589674 }], [ { "name": "test_visibility.test", "service": "test-test", "resource": "m2_s2_t3", - "trace_id": 17, + "trace_id": 43, "span_id": 1, "parent_id": 0, "type": "test", @@ -1522,7 +3304,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1533,7 +3315,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1544,19 +3326,18 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.codeowners": "[\"@romain\"]", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s2_t3", @@ -1564,9 +3345,9 @@ "test.status": "pass", "test.suite": "m2_s2", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test" }, "metrics": { @@ -1574,19 +3355,19 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495, + "process_id": 97018, "test.source.end": 12, "test.source.start": 4 }, - "duration": 59625, - "start": 1733391714565399292 + "duration": 37250, + "start": 1734010946947654507 }], [ { "name": "test_visibility.test", "service": "test-test", "resource": "m2_s2_t4", - "trace_id": 18, + "trace_id": 44, "span_id": 1, "parent_id": 0, "type": "test", @@ -1596,7 +3377,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1607,7 +3388,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1618,27 +3399,26 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", "test.framework_version": "1.0.0", - "test.is_new": "true", "test.module": "m2", "test.module_path": "", "test.name": "m2_s2_t4", "test.status": "pass", "test.suite": "m2_s2", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test" }, "metrics": { @@ -1646,17 +3426,17 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 32292, - "start": 1733391714565524000 + "duration": 20250, + "start": 1734010946947733340 }], [ { "name": "test_visibility.test", "service": "test-test", "resource": "m2_s2_t5", - "trace_id": 19, + "trace_id": 45, "span_id": 1, "parent_id": 0, "type": "test", @@ -1666,7 +3446,7 @@ "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://test.test.io/Test/test-test/test-test\",\"CI_PIPELINE_ID\":\"43949931\",\"CI_JOB_ID\":\"633358062\"}", "_dd.origin": "ciapp-test", "_dd.p.dm": "-0", - "_dd.p.tid": "6751756200000000", + "_dd.p.tid": "675ae84200000000", "ci.job.name": "test-job", "ci.job.url": "https://test.test.io/Test/test-test/test-test/-/jobs/633358062", "ci.node.labels": "[\"runner:test-test-test-test\"]", @@ -1677,7 +3457,7 @@ "ci.pipeline.url": "https://test.\u2020est.io/Test/test-\u2020est/test-test/-/pipelines/43949931", "ci.provider.name": "gitlab", "ci.stage.name": "test-stage", - "ci.workspace_path": "/tmp/pytest-of-root/pytest-12/test_manual_api_fake_efd_faulty_session0", + "ci.workspace_path": "/tmp/pytest-of-root/pytest-87/test_manual_api_fake_efd_faulty_session0", "component": "dd_manual_test_fw", "git.branch": "test.brancn/test_name", "git.commit.author.date": "2024-09-10T10:11:13+01:00", @@ -1688,13 +3468,13 @@ "git.repository_url": "https://test.test.io/Test/test-test/test-test.git", "git.tag": "v1.0.0", "language": "python", - "library_version": "2.18.0.dev124+gc03b9e422.d20241205", + "library_version": "2.18.0.dev111+g4ca600932", "os.architecture": "aarch64", "os.platform": "Linux", "os.version": "6.6.12-linuxkit", - "runtime-id": "bdef4ecb6c674245bfc4f6518ff5a773", + "runtime-id": "096f74b542c341ea9f8a4b6947a6a95f", "runtime.name": "CPython", - "runtime.version": "3.9.19", + "runtime.version": "3.11.9", "span.kind": "test", "test.command": "manual_efd_faulty_session", "test.framework": "dd_manual_test_fw", @@ -1705,9 +3485,9 @@ "test.status": "pass", "test.suite": "m2_s2", "test.type": "test", - "test_module_id": "17675353669520667242", - "test_session_id": "15705414272000062156", - "test_suite_id": "599512189588521228", + "test_module_id": "17061851581233455560", + "test_session_id": "18323133602450366815", + "test_suite_id": "6307099272916499859", "type": "test" }, "metrics": { @@ -1715,8 +3495,8 @@ "_dd.top_level": 1, "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, - "process_id": 32495 + "process_id": 97018 }, - "duration": 30792, - "start": 1733391714565625125 + "duration": 19708, + "start": 1734010946947794757 }]] From 99e9aff376a71ceee19264cf79e99e695495f8aa Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 12 Dec 2024 19:00:06 +0100 Subject: [PATCH 290/372] fix(asm): use unpatched json loads internally (#11688) --- ddtrace/appsec/_utils.py | 5 ++--- ddtrace/internal/_unpatched.py | 1 + .../fix-appsec-use-unpatched-json-8d09aacad4808ef2.yaml | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-appsec-use-unpatched-json-8d09aacad4808ef2.yaml diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index e2d46fe098e..bb8739654c5 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -5,6 +5,7 @@ from ddtrace.appsec._constants import API_SECURITY from ddtrace.appsec._constants import APPSEC +from ddtrace.internal._unpatched import unpatched_json_loads from ddtrace.internal.compat import to_unicode from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.http import _get_blocked_template # noqa:F401 @@ -17,8 +18,6 @@ def parse_response_body(raw_body): - import json - import xmltodict from ddtrace.appsec import _asm_request_context @@ -54,7 +53,7 @@ def access_body(bd): try: # TODO handle charset if "json" in content_type: - req_body = json.loads(access_body(raw_body)) + req_body = unpatched_json_loads(access_body(raw_body)) elif "xml" in content_type: req_body = xmltodict.parse(access_body(raw_body)) else: diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py index c226379f759..e209f30ff2a 100644 --- a/ddtrace/internal/_unpatched.py +++ b/ddtrace/internal/_unpatched.py @@ -1,6 +1,7 @@ # Acquire a reference to the open function from the builtins module. This is # necessary to ensure that the open function can be used unpatched when required. from builtins import open as unpatched_open # noqa +from json import loads as unpatched_json_loads # noqa # Acquire a reference to the threading module. Some parts of the library (e.g. # the profiler) might be enabled programmatically and therefore might end up diff --git a/releasenotes/notes/fix-appsec-use-unpatched-json-8d09aacad4808ef2.yaml b/releasenotes/notes/fix-appsec-use-unpatched-json-8d09aacad4808ef2.yaml new file mode 100644 index 00000000000..a4784672021 --- /dev/null +++ b/releasenotes/notes/fix-appsec-use-unpatched-json-8d09aacad4808ef2.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + ASM: This fix resolves an issue where AppSec was using a patched JSON loads, creating telemetry errors. From ae0547ef8da4dd6b2c9d3545e05593eff6e6f5e3 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:15:55 +0100 Subject: [PATCH 291/372] chore(ci_visibility): make codeowners file relative to repo path (#11696) This fixes an issue in the new version of the `pytest` plugin where `CODEOWNERS` parsing was incorrect due to the plugin sending absolute paths instead of paths relative to the repo root. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_plugin_v2.py | 10 +++++++++- ddtrace/internal/ci_visibility/recorder.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index b9cf7daf564..d3825578d7a 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -256,8 +256,16 @@ def _pytest_collection_finish(session) -> None: InternalTestSuite.discover(suite_id) item_path = Path(item.path if hasattr(item, "path") else item.fspath).absolute() + workspace_path = InternalTestSession.get_workspace_path() + if workspace_path: + try: + repo_relative_path = item_path.relative_to(workspace_path) + except ValueError: + repo_relative_path = item_path + else: + repo_relative_path = item_path - item_codeowners = InternalTestSession.get_path_codeowners(item_path) + item_codeowners = InternalTestSession.get_path_codeowners(repo_relative_path) if repo_relative_path else None source_file_info = _get_source_file_info(item, item_path) diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 12bb3688dad..225221a4a7d 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -1031,7 +1031,7 @@ def _on_session_get_path_codeowners(path: Path) -> Optional[List[str]]: codeowners = CIVisibility.get_codeowners() if codeowners is None: return None - return codeowners.of(str(path.absolute())) + return codeowners.of(str(path)) def _register_session_handlers(): From 30a8c6dc95a64fcd66be04814a373b513a432357 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 12 Dec 2024 13:31:50 -0500 Subject: [PATCH 292/372] ci: use mocks for GitHub calls in needs_testrun.py tests (#11692) Avoid hitting rate limits when testing this script, especially when the data/responses from GitHub API should be static anyways. Avoids: https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/736522083 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/151a249.txt | 20 + .riot/requirements/196755b.txt | 20 - .riot/requirements/4d1fa34.txt | 20 + .riot/requirements/b2ac981.txt | 22 - hatch.toml | 2 + riotfile.py | 2 + scripts/needs_testrun.py | 45 +- scripts/vcr/needs_testrun.yaml | 23996 +++++++++++++++++++++++++++++++ 8 files changed, 24076 insertions(+), 51 deletions(-) create mode 100644 .riot/requirements/151a249.txt delete mode 100644 .riot/requirements/196755b.txt create mode 100644 .riot/requirements/4d1fa34.txt delete mode 100644 .riot/requirements/b2ac981.txt create mode 100644 scripts/vcr/needs_testrun.yaml diff --git a/.riot/requirements/151a249.txt b/.riot/requirements/151a249.txt new file mode 100644 index 00000000000..e43376d1755 --- /dev/null +++ b/.riot/requirements/151a249.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/151a249.in +# +attrs==24.2.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +lxml==5.3.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +ruamel-yaml==0.18.6 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/196755b.txt b/.riot/requirements/196755b.txt deleted file mode 100644 index 250298e3848..00000000000 --- a/.riot/requirements/196755b.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/196755b.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -ruamel-yaml==0.18.6 -ruamel-yaml-clib==0.2.8 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/4d1fa34.txt b/.riot/requirements/4d1fa34.txt new file mode 100644 index 00000000000..e4b768d197a --- /dev/null +++ b/.riot/requirements/4d1fa34.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4d1fa34.in +# +attrs==24.2.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +lxml==5.3.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +ruamel-yaml==0.18.6 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/b2ac981.txt b/.riot/requirements/b2ac981.txt deleted file mode 100644 index b9c0f587e1e..00000000000 --- a/.riot/requirements/b2ac981.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/b2ac981.in -# -attrs==23.1.0 -coverage[toml]==7.2.7 -exceptiongroup==1.1.2 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.1 -pluggy==1.2.0 -pytest==7.4.0 -pytest-cov==4.1.0 -pytest-mock==3.11.1 -ruamel-yaml==0.17.32 -ruamel-yaml-clib==0.2.7 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/hatch.toml b/hatch.toml index 7bc7e107c04..f22870e01f1 100644 --- a/hatch.toml +++ b/hatch.toml @@ -133,8 +133,10 @@ _ = [ detached = true python = "3.10" extra-dependencies = [ + "lxml==5.3.0", "packaging==23.1", "ruamel.yaml==0.18.6", + "vcrpy==6.0.2", ] [envs.scripts.scripts] diff --git a/riotfile.py b/riotfile.py index 1a1f65ed116..b12bfcc1181 100644 --- a/riotfile.py +++ b/riotfile.py @@ -116,6 +116,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pys=["3"], pkgs={ "ruamel.yaml": latest, + "lxml": latest, }, ), Venv( @@ -124,6 +125,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pys=["3"], pkgs={ "ruamel.yaml": latest, + "lxml": latest, }, ), Venv( diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index 99ebba2c18c..bbbd73b33db 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -8,7 +8,6 @@ import logging import os from pathlib import Path -import re from subprocess import check_output import sys import typing as t @@ -16,6 +15,8 @@ from urllib.request import Request from urllib.request import urlopen +from lxml import html + sys.path.insert(0, str(Path(__file__).parents[1])) @@ -26,20 +27,34 @@ LOGGER = logging.getLogger(__name__) -BASE_BRANCH_PATTERN = re.compile(r':([^<]+)') - @cache def get_base_branch(pr_number: int) -> str: """Get the base branch of a PR - >>> get_base_branch(6412) + >>> import vcr + >>> with vcr.use_cassette( + ... "scripts/vcr/needs_testrun.yaml", + ... filter_headers=["authorization", "user-agent"], + ... record_mode="all"): + ... get_base_branch(6412) + ... get_base_branch(11534) + ... get_base_branch(11690) '1.x' + '2.15' + 'main' """ pr_page_content = urlopen(f"https://github.com/DataDog/dd-trace-py/pull/{pr_number}").read().decode("utf-8") - return BASE_BRANCH_PATTERN.search(pr_page_content).group(1) + tree = html.fromstring(pr_page_content) + base_ref = tree.find_class("base-ref") + if base_ref: + ref = base_ref[0].text_content().strip() + # We might have `DataDog:1.x` or `DataDog:main` so we need to strip the prefix + _, _, ref = ref.rpartition(":") + return ref.strip() + return "main" @cache @@ -116,7 +131,12 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] or if there is a specific SHA given, use the less accurate method of diffing against a base commit, either the given SHA or the merge-base. - >>> sorted(get_changed_files(6388)) # doctest: +NORMALIZE_WHITESPACE + >>> import vcr + >>> with vcr.use_cassette( + ... "scripts/vcr/needs_testrun.yaml", + ... filter_headers=["authorization", "user-agent"], + ... record_mode="all"): + ... sorted(get_changed_files(6388)) # doctest: +NORMALIZE_WHITESPACE ['ddtrace/debugging/_expressions.py', 'releasenotes/notes/fix-debugger-expressions-none-literal-30f3328d2e386f40.yaml', 'tests/debugging/test_expressions.py'] @@ -141,12 +161,19 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bool: """Check if a testrun is needed for a suite and PR - >>> needs_testrun("debugger", 6485) + >>> import vcr + >>> with vcr.use_cassette( + ... "scripts/vcr/needs_testrun.yaml", + ... filter_headers=["authorization", "user-agent"], + ... record_mode="all"): + ... needs_testrun("debugger", 6485) + ... needs_testrun("debugger", 6388) + ... needs_testrun("foobar", 6412) + ... needs_testrun("profile", 11690) True - >>> needs_testrun("debugger", 6388) True - >>> needs_testrun("foobar", 6412) True + False """ if "itr:noskip" in get_latest_commit_message().lower(): return True diff --git a/scripts/vcr/needs_testrun.yaml b/scripts/vcr/needs_testrun.yaml new file mode 100644 index 00000000000..f68dca107eb --- /dev/null +++ b/scripts/vcr/needs_testrun.yaml @@ -0,0 +1,23996 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/6412 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n ci: run the debugger suite only if necessary by P403n1x87 + \xB7 Pull Request #6412 \xB7 DataDog/dd-trace-py \xB7 GitHub\n\n\n\n + \ \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n ci: + run the debugger suite only if necessary\n #6412\n

\n
\n
\n\n
\n
\n \n + \ Merged\n\n
\n\n\n\n\n + \
\n P403n1x87\n + \ merged 7 commits into\n\n\n DataDog:1.x\n\nfrom\n\nP403n1x87:ci/debugger-suitespec\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Jul 25, + 2023\n\n\n
\n
\n\n\n \n\n\n\n
\n
\n
\n
\n + \
\n \n Merged\n\n + \
\n\n\n\n\n
\n + \

\n \n ci: run the debugger suite only if necessary\n \n + \ #6412\n

\n\n + \
\n P403n1x87\n merged 7 commits into\n\n\n DataDog:1.x\n\nfrom\n\nP403n1x87:ci/debugger-suitespec\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Jul 25, + 2023\n\n\n
\n
\n
\n + \
\n
\n
\n
\n
\n\n\n\n + \ \n + \ \n\n\n + \ \n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"P403n1x87\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \"@P403n1x87\"\n\n \n + \ P403n1x87\n \n\n \n\n \n\n commented\n\n\n + \ Jul + 20, 2023\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited by majorgreys\n + \ \n \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

We introduce the concept + of suitespec as a way of describing how sources affect test runs. We use it + to ensure that the debugger tests run only if sources that the suite depends + on are modified by the current commit.

\n

Suitespec Implementation + Details

\n

The suitespec solution is based on a manual + configuration of of test suites. To simplify the declaration of file patterns + for test suites, one can make use of components, which essentially + are a logic collection of patterns. Test suite can then be declared as a list + of components to reflect their dependencies on these logic parts, and to DRY + the declaration itself by avoiding repetitions.

\n

Notes

\n
    \n
  • When the script fails for any reason, tests are run.
  • \n
  • It + is important that path patterns are listed correctly, or some tests might + not run when they are in fact supposed to.
  • \n
  • Best effort to determine + the correct list of changed files via the GitHub REST API. When that fails, + we fall back to the less accurate git diff + against the target branch.
  • \n
\n

Checklist

\n
    \n
  • Change(s) + are motivated and described in the PR description.
  • \n
  • Testing strategy is described if automated tests are not included + in the PR.
  • \n
  • Risk is outlined + (performance impact, potential for breakage, maintainability, etc).
  • \n
  • Change is maintainable (easy to change, telemetry, documentation).
  • \n
  • Library release note guidelines are followed. If no release + note is required, add label changelog/no-changelog.
  • \n
  • Documentation is included (in-code, generated user docs, public corp docs).
  • \n
  • Backport labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Title + is accurate.
  • \n
  • No unnecessary + changes are introduced.
  • \n
  • Description + motivates each change.
  • \n
  • Avoids + breaking API changes unless absolutely necessary.
  • \n
  • Testing strategy adequately addresses listed risk(s).
  • \n
  • Change is maintainable (easy to change, telemetry, documentation).
  • \n
  • Release note makes sense to a user of the library.
  • \n
  • Reviewer has explicitly acknowledged and discussed the performance + implications of this PR as reported in the benchmarks PR comment.
  • \n
  • Backport labels are set in a manner that is consistent with + the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n \n
\n + \
\n \n \n\n
\n
\n\n + \ \n\n \"@P403n1x87\"\nP403n1x87\n\n\n\n\n added\n the \n\n changelog/no-changelog\n\n A changelog + entry is not required for this PR.\n label\n\n\n Jul 20, 2023\n\n
\n
\n\n\n\n\n
\n\n + \
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n riotfile.py\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 4 times, most recently\n from\n 8953a58 + \ to\n 575d15e + \ \n + \ Compare\n \n\n\n\n July 20, 2023 13:13 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n
\n + \ \n scripts/needs_testrun.py\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@emmettbutler\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Collaborator\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n emmettbutler\n + \ \n\n \n\n \n\n commented\n\n\n Jul 20, 2023\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n + \ \n
\n + \

I love this idea!

\n
\n
\n\n\n
\n\n + \ \n\n
\n
\n + \
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n + \
\n + \ \n \n
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n
\n\n

\n
\n \"@brettlangdon\"\n\n \n brettlangdon\n \n\n \n\n \n\n + \ left a comment\n\n\n\n\n \n
\n\n

\n
\n \n\n
\n
\n + \ \n
\n \n \n\n

Choose a reason for hiding this comment

\n\n + \

\n The reason will be displayed to describe this + comment to others. Learn more.\n + \

\n\n
\n \n \n
\n\n + \ \n
\n\n \n
\n

I know @gnufede was trying to get CI Visibility + running for this repo, if we go that route, we might be able to ITR ?

\n + \
\n
\n \n
\n\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n + \
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n
\n \n \n
\n
\n
\n + \ \n tests/.suitespec.json\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@P403n1x87\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n Author\n\n\n
\n\n

\n
\n \n\n \n + \ P403n1x87\n \n\n \n\n \n\n commented\n\n\n + \ Jul 20, 2023\n\n\n \n
\n\n + \

\n
\n\n\n
\n\n \n\n + \ \n \n \n \n + \ \n
\n
\n

I know @gnufede + was trying to get CI Visibility running for this repo, if we go that route, + we might be able to ITR ?

\n
\n

My understanding + is that ITR is a per-test rather than per-test-suite. So I see ITR improving + this even further rather than an alternative?

\n
\n
\n\n\n
\n\n + \ \n\n
\n
\n
\n \n \n
\n + \ emmettbutler reacted with thumbs up emoji\n + \
\n \n + \
\n
\n
\n
\n
\n + \
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 3 times, most recently\n from\n 713167a + \ to\n e8c3ecc + \ \n + \ Compare\n \n\n\n\n July 20, 2023 17:15 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@emmettbutler\"\n emmettbutler\n\n\n self-requested a review\n\n\n + \ July 20, 2023 21:23 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n
\"@P403n1x87\"\n P403n1x87\n\n\n dismissed\n emmettbutler\u2019s stale review\n\n\n + \ via\n \n 4e53e79\n + \ \n\n July + 21, 2023 09:41 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n e8c3ecc + \ to\n 4e53e79 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 09:41 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 21, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n
\n + \ \n .circleci/config.yml\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 3 times, most recently\n from\n d2671c5 + \ to\n 19b0da0 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 10:35 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n marked this pull request as + ready for review\n\n July + 21, 2023 10:41 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n a team\n\n as code owners\n\n\n + \ July 21, 2023 10:41 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n majorgreys, + \n jbertran, + \n brettlangdon, + \n emmettbutler + and \n a team\n\n\n\n July 21, 2023 10:41 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n af236d7 + \ to\n a4c0000 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 15:26 \n + \ \n
\n
\n\n\n
\n\n\n
\n + \
\n
\n
\n \n \n
\n
\n + \
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 2 times, most recently\n from\n c50870c + \ to\n e812418 + \ \n + \ Compare\n \n\n\n\n July 24, 2023 12:52 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n + \ \n
\n \n
\n
\"@brettlangdon\"\n brettlangdon\n\n\n dismissed\n emmettbutler\u2019s stale review\n\n\n + \ via\n \n cdb1444\n + \ \n\n July + 24, 2023 16:44 \n \n
\n
\n\n\n
\n\n + \
\n \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n brettlangdon + and \n emmettbutler\n\n\n\n + \ July 24, 2023 18:57 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n + \ \n
\n
\n \n
\n \n
\n + \
P403n1x87\n \n\n added 5 commits\n + \ July 24, 2023 22:13
\n
+ \
\n
\n + \ \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 0d844de\n \n
\n
\n + \
\n
\n
We introduce the concept of suitespec as a way of describing
+        how\nsources affect test runs. We use it to ensure that the debugger\ntests
+        run only if sources that the suite depends on are modified\nby the current
+        commit.
\n
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 1ffab15\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n web + scraping FTW\n \n\n
\n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ a115763\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n add + doctests\n \n\n
\n\n
\n \n\n + \ \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 4d0fb2e\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n use + dynamic config\n \n\n
\n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 690a7b1\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \n
\n
\"@P403n1x87\"\n P403n1x87\n\n\n dismissed stale reviews from + emmettbutler + and brettlangdon\n\n\n + \ via\n \n 690a7b1\n + \ \n\n July + 24, 2023 21:17 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n 5f1daca + \ to\n 690a7b1 + \ \n + \ Compare\n \n\n\n\n July 24, 2023 21:17 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n emmettbutler + and \n brettlangdon\n\n\n\n + \ July 24, 2023 21:17 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n\n\n + \
\n + \ \n
\n
\n \n
\n \n
\n + \
P403n1x87\n \n\n added 2 commits\n + \ July 25, 2023 09:06
\n
+ \
\n
\n + \ \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ f421ece\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 3eacc26\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n + \ \n + \ \n\n + \
\n
\n\n\n \"@P403n1x87\"\n P403n1x87\n\n\n\n merged commit f441242\n into\n\n \n \n DataDog:1.x\n + \ \n\n\n Jul 25, 2023\n\n
\n
\n\n
\n\n
\n
\n \n \n\n
\n\n
\n
\n \"@Yun-Kim\"\nYun-Kim\n\n\n\n mentioned this pull request\n \n Jul 26, 2023\n + \ \n
\n\n\n\n\n \n
\n \n \n \n\n \n \n\n + \ \n \n \n \n\n\n 16 + tasks\n
\n
\n\n\n\n
\n
\n\n + \ \n
\n \n + \ \n + \ \n\n \n
\n \n Yun-Kim \n\n added a commit\n that referenced\n + \ this pull request\n\n \n + \ Jul + 26, 2023\n \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@Yun-Kim\"\n + \
\n
\n\n\n \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n\n \n + \
\n \n 43497d1\n \n
\n
\n + \
\n
\n
#6412
+        changed our circleci configuration setup to be dynamic, but this\ninadvertently
+        removed the `coverage` and `riot_run_latest` circleci\npipeline parameters
+        from the main `.circleci/config.yml` file, which\nbreaks our nightly 1.x coverage
+        pipeline runs. This PR re-adds those\nparameters back and re-enables coverage
+        reporting.\n\nNote that `datastreams`, `langchain`, `elasticsearch`,\n`integration-snapshot`
+        test suites are still failing on 1.x nightly\ncoverage runs and will need
+        to be fixed.\n\n## Checklist\n\n- [x] Change(s) are motivated and described
+        in the PR description.\n- [x] Testing strategy is described if automated tests
+        are not included\nin the PR.\n- [x] Risk is outlined (performance impact,
+        potential for breakage,\nmaintainability, etc).\n- [x] Change is maintainable
+        (easy to change, telemetry, documentation).\n- [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [x] Title is accurate.\n- [x] No unnecessary changes
+        are introduced.\n- [x] Description motivates each change.\n- [x] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [x] Testing strategy adequately addresses
+        listed risk(s).\n- [x] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [x] Release note makes sense to a user of the library.\n-
+        [x] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [x] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n \n
\n \n \n \n\n \n
\n + \ \n romainkomorndatadog + \n\n pushed a commit\n that referenced\n this pull request\n\n + \ \n Aug 8, 2023\n + \ \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@P403n1x87\"\n + \ \n \"@romainkomorndatadog\"\n + \
\n
\n\n\n
\n + \ \n ci: + run the debugger suite only if necessary (#6412)\n + \ \n\n \n + \ \n \n\n
\n\n + \
\n \n\n \n \n \n\n \n\n
\n\n
\n
\n\n \n
\n + \ \n 6838e4b\n \n
\n
\n + \
\n
\n
We introduce the concept of suitespec as a way of describing
+        how sources\naffect test runs. We use it to ensure that the debugger tests
+        run only\nif sources that the suite depends on are modified by the current
+        commit.\n\n## Suitespec Implementation Details\n\nThe suitespec solution is
+        based on a manual configuration of of test\nsuites. To simplify the declaration
+        of file patterns for test suites,\none can make use of _components_, which
+        essentially are a logic\ncollection of patterns. Test suite can then be declared
+        as a list of\ncomponents to reflect their dependencies on these logic parts,
+        and to\nDRY the declaration itself by avoiding repetitions.\n\n## Notes\n\n-
+        When the script fails for any reason, tests are run.\n- It is important that
+        path patterns are listed correctly, or some tests\nmight not run when they
+        are in fact supposed to.\n- Best effort to determine the correct list of changed
+        files via the\nGitHub REST API. When that fails, we fall back to the less
+        accurate `git\ndiff` against the target branch.\n\n## Checklist\n\n- [x] Change(s)
+        are motivated and described in the PR description.\n- [x] Testing strategy
+        is described if automated tests are not included\nin the PR.\n- [x] Risk is
+        outlined (performance impact, potential for breakage,\nmaintainability, etc).\n-
+        [x] Change is maintainable (easy to change, telemetry, documentation).\n-
+        [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [ ] Title is accurate.\n- [ ] No unnecessary changes
+        are introduced.\n- [ ] Description motivates each change.\n- [ ] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [ ] Testing strategy adequately addresses
+        listed risk(s).\n- [ ] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [ ] Release note makes sense to a user of the library.\n-
+        [ ] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [ ] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n \n
\n \n \n \n\n \n
\n + \ \n romainkomorndatadog + \n\n pushed a commit\n that referenced\n this pull request\n\n + \ \n Aug 8, 2023\n + \ \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@Yun-Kim\"\n + \ \n \"@romainkomorndatadog\"\n + \
\n
\n\n\n \n\n + \
\n \n\n \n \n \n\n \n\n
\n\n
\n
\n\n \n
\n + \ \n b38e5ce\n \n
\n
\n + \
\n
\n
#6412
+        changed our circleci configuration setup to be dynamic, but this\ninadvertently
+        removed the `coverage` and `riot_run_latest` circleci\npipeline parameters
+        from the main `.circleci/config.yml` file, which\nbreaks our nightly 1.x coverage
+        pipeline runs. This PR re-adds those\nparameters back and re-enables coverage
+        reporting.\n\nNote that `datastreams`, `langchain`, `elasticsearch`,\n`integration-snapshot`
+        test suites are still failing on 1.x nightly\ncoverage runs and will need
+        to be fixed.\n\n## Checklist\n\n- [x] Change(s) are motivated and described
+        in the PR description.\n- [x] Testing strategy is described if automated tests
+        are not included\nin the PR.\n- [x] Risk is outlined (performance impact,
+        potential for breakage,\nmaintainability, etc).\n- [x] Change is maintainable
+        (easy to change, telemetry, documentation).\n- [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [x] Title is accurate.\n- [x] No unnecessary changes
+        are introduced.\n- [x] Description motivates each change.\n- [x] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [x] Testing strategy adequately addresses
+        listed risk(s).\n- [x] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [x] Release note makes sense to a user of the library.\n-
+        [x] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [x] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n\n\n
\n\n\n\n \n
\n
\n \n
+ \
\n\n\n\n \n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n \n\n\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@brettlangdon\"\n \n brettlangdon\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n brettlangdon approved these changes\n\n + \

\n

\n \n\n \n \"@majorgreys\"\n \n majorgreys\n\n\n + \ Awaiting requested review from majorgreys\n\n + \ majorgreys is a code owner automatically + assigned from DataDog/apm-core-python\n\n \n

\n + \

\n \n\n \n \"@jbertran\"\n \n jbertran\n\n\n + \ Awaiting requested review from jbertran\n\n + \ jbertran was automatically assigned from + DataDog/apm-framework-integrations-reviewers-py\n\n \n

\n + \

\n \n\n \n \"@emmettbutler\"\n \n emmettbutler\n\n\n + \ Awaiting requested review from emmettbutler\n\n\n + \ \n

\n\n \n
\n\n
\n\n\n
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n \n\n changelog/no-changelog\n\n + \ A changelog entry is not required for + this PR.\n\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 4 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n\n\n\n\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:36 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=fQw%2BTZMS4QYZ%2FTA4MNOPSJubEJj6%2B4YAbvZcDJw6R8TTK%2BMVMvH7EZQtu30ktX%2By%2FA6TPcH4dFe9WAR0%2B6WdXM5LeWe7eUOeosO%2FKdcYGMtaudvPV7Tjrv8NPxefhK8GYTzCAI0TN6iQR7CC7S4bKt21Me3zMtaqQlfrbOvexXVbatPyfKM1pSwdQDSYNgXgZpvz6FpudZu8Ito5%2FSqD%2F6P%2B%2Foq57qdkGtm98SrAr1VET3ZWzxV9a2jYhwXfpCKzqaZa4CrIRiFSjs65m6mZvg%3D%3D--qdQ52Vjfe00bTqax--8KxHEOVAdiZ0ixENBtHaQg%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.1954075846.1734014555; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:35 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:35 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED1F:3C75F0:23D0577:323E7BD:675AF65B + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=259.185408,conversation_content-fragment;desc="conversation_content + fragment";dur=1167.36918,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=278.203377,nginx;desc="NGINX";dur=1.232025,glb;desc="GLB";dur=3.090931 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/11534 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n fix(asm): add global states to ensure patching once [backport + 2.15] by christophe-papazian \xB7 Pull Request #11534 \xB7 DataDog/dd-trace-py + \xB7 GitHub\n\n\n\n \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n fix(asm): + add global states to ensure patching once [backport 2.15]\n #11534\n

\n
\n
\n\n + \
\n + \
\n + \ \n Merged\n\n + \
\n\n\n\n\n
\n gnufede\n merged 3 commits into\n\n\n 2.15\n\nfrom\n\nbackport-11522-to-2.15\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Nov 26, + 2024\n\n\n
\n
\n\n\n \n\n\n\n
\n
\n
\n
\n + \
\n \n Merged\n\n + \
\n\n\n\n\n
\n + \

\n \n fix(asm): add global states to ensure patching once [backport + 2.15]\n \n #11534\n

\n\n + \
\n gnufede\n merged 3 commits into\n\n\n 2.15\n\nfrom\n\nbackport-11522-to-2.15\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Nov 26, + 2024\n\n\n
\n
\n
\n + \
\n
\n
\n
\n
\n\n\n\n + \ \n
\n
\n \n \n +74\n + \ \n \n \u221210\n + \ \n \n \n + \ \n \n
\n\n \n
\n\n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"christophe-papazian\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \"@christophe-papazian\"\n\n \n + \ christophe-papazian\n \n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited\n \n + \ \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

Backport 81824b8 + from #11522 to 2.15.

\n

Ensure common patches for SCA and Exploit Prevention are loaded..

\n

only once
\nonly if exploit prevention is active or sca is + active
\nChanges:

\n

factorize load_common_modules logic + in ddtrace.appsec
\nboolean state for patch_common_module and enable_iast_propagation + to ensure they are only called once.
\nensure it's loaded after one click + activation
\nensure it's properly loaded in unit tests if required
\nadd + some failsafe for iast in wrap_open for importerror
\nupdate an iast test + to reflect that common_modules is loaded in the test by default.
\nAPPSEC-55997

\n

Checklist

\n
    \n
  • PR author has checked that all the criteria below are met
  • \n
  • The + PR description includes an overview of the change
  • \n
  • The PR description + articulates the motivation for the change
  • \n
  • The change includes tests + OR the PR description describes a testing strategy
  • \n
  • The PR description + notes risks associated with the change, if any
  • \n
  • Newly-added code + is easy to change
  • \n
  • The change follows the library release note guidelines
  • \n
  • The change + includes or references documentation updates if necessary
  • \n
  • Backport + labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Reviewer + has checked that all the criteria below are met
  • \n
  • Title is accurate
  • \n
  • All + changes are related to the pull request's stated goal
  • \n
  • Avoids breaking + API changes
  • \n
  • Testing strategy adequately addresses + listed risks
  • \n
  • Newly-added code is easy to change
  • \n
  • Release + note makes sense to a user of the library
  • \n
  • If necessary, author has + acknowledged and discussed the performance implications of this PR as reported + in the benchmarks PR comment
  • \n
  • Backport labels are set in a manner + that is consistent with the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n + \ \n
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@christophe-papazian\"\n + \
\n
\n\n
\n \n + \ fix(asm): + add global states to ensure patching once (#11522)\n + \ \n\n \n + \ + \ \n
\n\n
\n \n\n + \ \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ cd59645\n \n
\n
\n + \
\n
\n
Ensure common patches for SCA and Exploit Prevention are loaded..\n-
+        only once\n- only if exploit prevention is active or sca is active\n\nChanges:\n-
+        factorize load_common_modules logic in ddtrace.appsec\n- boolean state for
+        patch_common_module and enable_iast_propagation to\nensure they are only called
+        once.\n- ensure it's loaded after one click activation\n- ensure it's properly
+        loaded in unit tests if required\n- add some failsafe for iast in wrap_open
+        for importerror\n- update an iast test to reflect that common_modules is loaded
+        in the\ntest by default.\n\nAPPSEC-55997\n\n- [x] PR author has checked that
+        all the criteria below are met\n- The PR description includes an overview
+        of the change\n- The PR description articulates the motivation for the change\n-
+        The change includes tests OR the PR description describes a testing\nstrategy\n-
+        The PR description notes risks associated with the change, if any\n- Newly-added
+        code is easy to change\n- The change follows the [library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\n-
+        The change includes or references documentation updates if necessary\n- Backport
+        labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n-
+        [x] Reviewer has checked that all the criteria below are met\n- Title is accurate\n-
+        All changes are related to the pull request's stated goal\n- Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges\n-
+        Testing strategy adequately addresses listed risks\n- Newly-added code is
+        easy to change\n- Release note makes sense to a user of the library\n- If
+        necessary, author has acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment\n- Backport labels are
+        set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)\n\n(cherry
+        picked from commit 81824b8)
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n marked + this pull request as ready for review\n\n November + 25, 2024 16:51 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n requested + review from\n a team\n\n + \ as code owners\n\n\n + \ November 25, 2024 16:51 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n requested + review from\n gnufede + and \n emmettbutler\n\n\n\n + \ November 25, 2024 16:51 \n + \ \n
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@github-actions\"\n\n \n + \ \"GitHub\n \n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n github-actions\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

CODEOWNERS have + been resolved as:

\n
releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml
+        \  @DataDog/apm-python\nddtrace/_monkey.py                                                      @DataDog/apm-core-python\nddtrace/appsec/__init__.py
+        \                                             @DataDog/asm-python\nddtrace/appsec/_common_module_patches.py
+        \                               @DataDog/asm-python\nddtrace/appsec/_iast/__init__.py
+        \                                       @DataDog/asm-python\nddtrace/appsec/_remoteconfiguration.py
+        \                                 @DataDog/asm-python\ntests/appsec/integrations/test_flask_telemetry.py
+        \                      @DataDog/asm-python\ntests/utils.py                                                          @DataDog/python-guild\n
\n\n + \
\n
\n\n\n
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@datadog-dd-trace-py-rkomorn\"\n\n
\n\n\n + \
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n datadog-dd-trace-py-rkomorn\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Datadog Report

\n

Branch report: + backport-11522-to-2.15
\nCommit + report: c476a58
\nTest + service: dd-trace-py

\n

\u2705 + 0 Failed, 592 Passed, 694 Skipped, 19m 30.54s Total duration (15m 23.31s time + saved)

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"gnufede\"\n + \
\n \n
\n + \
\n + \ \n gnufede\n \n\n + \ \n\n approved these changes\n\n\n \n \n + \ Nov + 25, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n \n
\n + \ \n
\n + \ \n
\n
\n \"@gnufede\"\n gnufede\n\nenabled + auto-merge (squash)\n\n November + 25, 2024 17:32 \n \n
\n
\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@pr-commenter\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n pr-commenter\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Benchmarks

\n

Benchmark execution + time: 2024-11-26 21:13:50

\n

Comparing candidate commit + c476a58 + in PR branch backport-11522-to-2.15 with + baseline commit b462888 + in branch 2.15.

\n

Found + 0 performance improvements and 0 performance regressions! Performance is the + same for 371 metrics, 53 unstable metrics.

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"erikayasuda\"\n + \
\n \n
\n + \
\n + \ \n erikayasuda\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Nov + 26, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n + \ \n
\n
\n \n
\n \n
\n + \
christophe-papazian\n \nand others\n + \ added 2 commits\n November + 26, 2024 18:39
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@christophe-papazian\"\n + \
\n
\n\n \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 3ac9ef8\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@erikayasuda\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ c476a58\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n + \ \n + \ \n\n + \
\n
\n\n \n + \ \"@gnufede\"\n gnufede\n\n\n\n + \ merged commit 2d6800f\n into\n\n \n \n 2.15\n \n\n\n Nov 26, 2024\n\n
\n 584 checks passed\n
\n\n
\n + \ \n \n + \ \n \n + \ \n\n + \ \n
\n
\n
\n\n
\n\n + \
\n \n
\n \n
\n
\"@gnufede\"\n gnufede\n\n\n + \ \n deleted the\n \n + \ \n backport-11522-to-2.15\n \n branch\n\n + \ November 26, 2024 21:16 \n + \ \n
\n
\n\n\n
\n\n\n\n\n\n \n
\n
\n \n
+ \
\n\n\n\n
\n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n
\n\n
\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@erikayasuda\"\n \n erikayasuda\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n erikayasuda approved these changes\n\n + \

\n

\n \n\n \n \"@gnufede\"\n \n gnufede\n\n\n\n \n + \ \n \n + \ \n\n \n \n gnufede approved these changes\n\n + \

\n

\n \n\n \n \"@emmettbutler\"\n \n emmettbutler\n\n\n + \ Awaiting requested review from emmettbutler\n\n + \ emmettbutler is a code owner automatically + assigned from DataDog/apm-python\n\n \n

\n\n \n
\n\n
\n\n\n + \
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n None yet\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 3 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n
\n\n\n\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:37 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=LtiHLNx8mstCD1%2F8GdlLK3Ek4%2FUx0Fe2Z5G%2BgyD3AJIfkjlnrgBVvR4nRGY7DTatKP%2Bou1B2HQOEbvPrmsRQSzNr4QrkXD%2B%2BoelH3OrGoVb5p8iCoqQMgEy0wWGa1LZNg6ElbtORrY%2BOTZc3pcswIwJXzwyf5B41ot6LyczBcI7LxdQXLwION06Cw9M4GChczVf00HfGJq85K%2FijVuPAL%2BSNpc0CpSymS4zbxOOTeM85%2BMUXqmgfjypU8Hdl1TUYqKHqDF25MpY1LOSlKhlLLw%3D%3D--xSnv%2BlNibojh5RSX--pzM3%2Fm4gngMObk6H3%2FTOfw%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.1210734268.1734014556; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:36 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:36 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED26:1035B4:234099A:312F92C:675AF65C + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=450.768495,conversation_content-fragment;desc="conversation_content + fragment";dur=576.513283,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=305.288275,nginx;desc="NGINX";dur=1.093278,glb;desc="GLB";dur=4.679312 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/11690 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n ci: store fake DD_API_KEY as a secret by brettlangdon \xB7 + Pull Request #11690 \xB7 DataDog/dd-trace-py \xB7 GitHub\n\n\n\n \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n ci: + store fake DD_API_KEY as a secret\n #11690\n + \

\n
\n
\n\n
\n
\n \n + Open\n\n
\n\n\n\n\n
\n brettlangdon\n\n wants to merge\n 1\n + \ commit into\n\n\n main\n\n + \
\n
\n + \ \n base:\n + \ main\n \n + \ \n \n + \ \n
\n
\n + \
\n Choose + a base branch\n \n
\n\n + \ \n
\n + \ \n
\n\n \n \n\n
\n \n\n \n\n \n\n\n
\n
\n \n + \ \n \n \n Loading\n\n + \
\n
\n\n \n\n\n \n\n + \
\n\n \n
\n + \
\n
\n
\n\n \n + \
\n
\n\n
\n \n
\n\nfrom\n\nbrettlangdon-patch-3\n \n \n \n\n \n \n\n + \
\n
\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n + \
\n\n
\n
\n\n\n \n\n\n\n
\n
\n + \
\n + \
\n + \
\n \n Open\n\n
\n\n\n\n\n + \
\n

\n \n + \ ci: store fake DD_API_KEY as a secret\n \n #11690\n

\n\n
\n brettlangdon\n\n + \ wants to merge\n 1\n + \ commit into\n\n\n main\n\nfrom\n\nbrettlangdon-patch-3\n \n \n \n\n \n \n\n + \
\n
\n\n\n\n\n + \
\n
\n
\n
\n + \
\n
\n
\n
\n\n\n\n \n
\n
\n \n \n +2\n \n \n \u22122\n \n \n + \ \n \n \n + \
\n\n \n
\n\n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"brettlangdon\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n
\n\n

\n
\n \"@brettlangdon\"\n\n \n brettlangdon\n \n\n \n\n \n\n + \ commented\n\n\n Dec 12, 2024\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited\n \n + \ \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

Checklist

\n
    \n
  • PR author + has checked that all the criteria below are met
  • \n
  • The PR description + includes an overview of the change
  • \n
  • The PR description articulates + the motivation for the change
  • \n
  • The change includes tests OR the PR + description describes a testing strategy
  • \n
  • The PR description notes + risks associated with the change, if any
  • \n
  • Newly-added code is easy + to change
  • \n
  • The change follows the library release note guidelines
  • \n
  • The change + includes or references documentation updates if necessary
  • \n
  • Backport + labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Reviewer + has checked that all the criteria below are met
  • \n
  • Title is accurate
  • \n
  • All + changes are related to the pull request's stated goal
  • \n
  • Avoids breaking + API changes
  • \n
  • Testing strategy adequately addresses + listed risks
  • \n
  • Newly-added code is easy to change
  • \n
  • Release + note makes sense to a user of the library
  • \n
  • If necessary, author has + acknowledged and discussed the performance implications of this PR as reported + in the benchmarks PR comment
  • \n
  • Backport labels are set in a manner + that is consistent with the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n + \ \n
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@brettlangdon\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ a6675d3\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \
\n\n \n\n \"@brettlangdon\"\nbrettlangdon\n\n\n\n\n added\n the \n\n changelog/no-changelog\n\n A changelog + entry is not required for this PR.\n label\n\n\n Dec 12, 2024\n\n
\n
\n\n\n + \
\n \n
\n \n
\n + \
\"@brettlangdon\"\n brettlangdon\n\n\n requested review from\n + \ a team\n\n as code owners\n\n\n + \ December 12, 2024 13:39 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@brettlangdon\"\n brettlangdon\n\n\n requested review from\n + \ avara1986 + and \n erikayasuda\n\n\n\n + \ December 12, 2024 13:39 \n + \ \n
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@github-actions\"\n\n \n + \ \"GitHub\n \n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n github-actions\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

CODEOWNERS have + been resolved as:

\n
.github/workflows/system-tests.yml
+        \                                     @DataDog/python-guild @DataDog/apm-core-python\n
\n\n + \
\n
\n\n\n
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"romainkomorndatadog\"\n + \
\n \n
\n + \
\n + \ \n romainkomorndatadog\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Dec + 12, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@datadog-dd-trace-py-rkomorn\"\n\n
\n\n\n + \
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n datadog-dd-trace-py-rkomorn\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Datadog Report

\n

Branch report: + brettlangdon-patch-3
\nCommit + report: a6675d3
\nTest + service: dd-trace-py

\n

\u2705 + 0 Failed, 55 Passed, 1413 Skipped, 1m 29.81s Total duration (35m 20.17s time + saved)

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@brettlangdon\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n Author\n\n\n + \
\n\n

\n
\n + \ \n\n \n brettlangdon\n + \ \n\n \n\n \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

/merge

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@dd-devflow\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n dd-devflow\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \ \n

Devflow running: /merge

\n

View all feedbacks in Devflow UI.

\n
\n

2024-12-12 13:54:30 UTC \u2139\uFE0F MergeQueue: + waiting for PR to be ready

\n

This merge request is not + mergeable yet, because of pending checks/missing approvals. It will be added + to the queue as soon as checks pass and/or get approvals.
\nNote: + if you pushed new commits since the last approval, you may need additional + approval.
\nYou can remove it from the waiting list with /remove + command.

\n

Use /merge -c + to cancel this operation!

\n
\n

2024-12-12 + 14:26:14 UTC \u2139\uFE0F MergeQueue: merge request + added to the queue

\n

The median merge time in main + is 34m.

\n

Use /merge -c + to cancel this operation!

\n
\n

\u23F3 + command still in progress ...

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \
\n\n \n\n \"@dd-devflow\"\ndd-devflow\nbot\n\n\n\n added\n the \n\n mergequeue-status: waiting\n\n label\n\n\n Dec 12, 2024\n\n
\n
\n\n\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@pr-commenter\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n pr-commenter\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Benchmarks

\n

Benchmark execution + time: 2024-12-12 14:24:20

\n

Comparing candidate commit + a6675d3 + in PR branch brettlangdon-patch-3 with + baseline commit 385d8e0 + in branch main.

\n

Found + 0 performance improvements and 0 performance regressions! Performance is the + same for 394 metrics, 2 unstable metrics.

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \ \n
\n\n\n\n\n
\n\n\n\n\n\n + \ \n
\n
\n \n
+ \
\n\n\n\n
\n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n
\n\n
\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@romainkomorndatadog\"\n \n romainkomorndatadog\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n romainkomorndatadog approved these changes\n\n + \

\n

\n \n\n \n \"@avara1986\"\n \n avara1986\n\n\n + \ Awaiting requested review from avara1986\n\n + \ avara1986 is a code owner automatically + assigned from DataDog/python-guild\n\n \n

\n

\n \n\n \n \"@erikayasuda\"\n \n erikayasuda\n\n\n + \ Awaiting requested review from erikayasuda\n\n + \ erikayasuda is a code owner automatically + assigned from DataDog/apm-core-python\n\n \n

\n\n + \ \n
\n\n
\n\n\n
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n \n\n changelog/no-changelog\n\n + \ A changelog entry is not required for + this PR.\n \n\n mergequeue-status: + in_progress\n\n\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 2 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n
\n\n\n
\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:38 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=goPCFokfo9CoHjAGnWH6245viFzykZOSTQZe2I4w0VI8O%2FBqLC9Xv8AW%2F6ZjmrAmWBiSwR%2BJfSgAxkI4KR6iJ7iP7KTOza9Z%2Fx3f69HoNCXVVOHyocDogP%2Bkm1AiUdpG5y74PTCPFqrxrAFXC27mPRlmZoEWOfCSWgl4YRkTZv70BAdIcjfmqhFa%2BtQhB0TltjWeDdF8qyOXZzTY7EorwqYP%2BPT%2FJYz2v61wLYsHH22O6rrrwLYlwr2P3x6Yb3Bx2aKM6eK975vB0hXOQtNMug%3D%3D--y2NTdNqEEkcwaCVD--Ce4x%2FRlMrMinpyEeKuACLQ%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.2015969099.1734014557; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:37 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:37 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED29:27C835:21B19AA:2F4A603:675AF65D + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=412.175919,conversation_content-fragment;desc="conversation_content + fragment";dur=448.910543,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=302.334653,nginx;desc="NGINX";dur=1.331055,glb;desc="GLB";dur=3.067062 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/6388/files?page=1 + response: + body: + string: '[{"sha":"1325b0864ebc6d4c40970f698018ac2524fe4e33","filename":"ddtrace/debugging/_expressions.py","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/ddtrace%2Fdebugging%2F_expressions.py","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/ddtrace%2Fdebugging%2F_expressions.py","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/ddtrace%2Fdebugging%2F_expressions.py?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -292,8 +292,8 @@ def _compile_operation(ast):\n \n def _compile_literal(ast):\n # + type: (DDASTType) -> Optional[List[Instr]]\n- # literal => | + true | false | \"string\"\n- if not isinstance(ast, (str, int, float, bool)):\n+ # + literal => | true | false | \"string\" | null\n+ if not (isinstance(ast, + (str, int, float, bool)) or ast is None):\n return None\n \n return + [Instr(\"LOAD_CONST\", ast)]"},{"sha":"b4517ad79f67a2b362360ae8e7e0b0b3fa2e4ea8","filename":"releasenotes/notes/fix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","status":"added","additions":4,"deletions":0,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -0,0 +1,4 @@\n+---\n+fixes:\n+ - |\n+ dynamic instrumentation: handle + null literal in conditions and expressions."},{"sha":"3c4d96fe66b871238c02651af82d43a1ad8085c3","filename":"tests/debugging/test_expressions.py","status":"modified","additions":1,"deletions":0,"changes":1,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/tests%2Fdebugging%2Ftest_expressions.py","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/tests%2Fdebugging%2Ftest_expressions.py","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/tests%2Fdebugging%2Ftest_expressions.py?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -72,6 +72,7 @@ def __getitem__(self, name):\n # Test argument predicates + and operations\n ({\"contains\": [{\"ref\": \"payload\"}, \"hello\"]}, + {\"payload\": \"hello world\"}, True),\n ({\"eq\": [{\"ref\": \"hits\"}, + True]}, {\"hits\": True}, True),\n+ ({\"eq\": [{\"ref\": \"hits\"}, + None]}, {\"hits\": None}, True),\n ({\"substring\": [{\"ref\": \"payload\"}, + 4, 7]}, {\"payload\": \"hello world\"}, \"hello world\"[4:7]),\n ({\"any\": + [{\"ref\": \"collection\"}, {\"isEmpty\": {\"ref\": \"@it\"}}]}, {\"collection\": + [\"foo\", \"bar\", \"\"]}, True),\n ({\"startsWith\": [{\"ref\": \"local_string\"}, + \"hello\"]}, {\"local_string\": \"hello world!\"}, True),"}]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '3264' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:38 GMT + ETag: + - '"85a10accfc7f3330efa4961171936a0e3ea39a94e59a1811461b17a9a610bdb4"' + Last-Modified: + - Sun, 08 Dec 2024 16:19:43 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED30:1C31C4:1AEE9EF:3582AA6:675AF65E + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4898' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '102' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/6388/files?page=2 + response: + body: + string: '[]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:38 GMT + ETag: + - '"4acd3c336ca9625e24fba0a2ea9cad06cf4693ace7e76d92c8a9a05f03c7b0cd"' + Last-Modified: + - Sun, 08 Dec 2024 16:19:43 GMT + Link: + - ; rel="prev", + ; rel="last", + ; rel="first" + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED34:27B12E:1B9E8A9:36ED322:675AF65E + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4897' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '103' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/11690/files?page=1 + response: + body: + string: '[{"sha":"ce795db4fe24584e0a3c105f6f130071b1292cbe","filename":".github/workflows/system-tests.yml","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/a6675d3799af44382bd5b677c56a94843a6433aa/.github%2Fworkflows%2Fsystem-tests.yml","raw_url":"https://github.com/DataDog/dd-trace-py/raw/a6675d3799af44382bd5b677c56a94843a6433aa/.github%2Fworkflows%2Fsystem-tests.yml","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/.github%2Fworkflows%2Fsystem-tests.yml?ref=a6675d3799af44382bd5b677c56a94843a6433aa","patch":"@@ + -54,7 +54,7 @@ jobs:\n # system-tests requires an API_KEY, but it does + not have to be a valid key, as long as we don''t run a scenario\n # + that make assertion on backend data. Using a fake key allow to run system + tests on PR originating from forks.\n # If ever it''s needed, a valid + key exists in the repo, using ${{ secrets.DD_API_KEY }}\n- DD_API_KEY: + 1234567890abcdef1234567890abcdef\n+ DD_API_KEY: ${{ secrets.FAKE_DD_API_KEY + }}\n CMAKE_BUILD_PARALLEL_LEVEL: 12\n SYSTEM_TESTS_AWS_ACCESS_KEY_ID: + ${{ secrets.IDM_AWS_ACCESS_KEY_ID }}\n SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: + ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }}\n@@ -106,7 +106,7 @@ jobs:\n # + system-tests requires an API_KEY, but it does not have to be a valid key, + as long as we don''t run a scenario\n # that make assertion on backend + data. Using a fake key allow to run system tests on PR originating from forks.\n # + If ever it''s needed, a valid key exists in the repo, using ${{ secrets.DD_API_KEY + }}\n- DD_API_KEY: 1234567890abcdef1234567890abcdef\n+ DD_API_KEY: + ${{ secrets.FAKE_DD_API_KEY }}\n CMAKE_BUILD_PARALLEL_LEVEL: 12\n SYSTEM_TESTS_AWS_ACCESS_KEY_ID: + ${{ secrets.IDM_AWS_ACCESS_KEY_ID }}\n SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: + ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }}"}]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '1930' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:39 GMT + ETag: + - '"e91026bdc9aa216ff163739444e03dfcf4e719131166fd717d6e5a7eafbd54fe"' + Last-Modified: + - Thu, 12 Dec 2024 14:26:20 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED39:1C31C4:1AEEBE6:3582E96:675AF65E + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4896' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '104' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/11690/files?page=2 + response: + body: + string: '[]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:39 GMT + ETag: + - '"4acd3c336ca9625e24fba0a2ea9cad06cf4693ace7e76d92c8a9a05f03c7b0cd"' + Last-Modified: + - Thu, 12 Dec 2024 14:26:20 GMT + Link: + - ; rel="prev", + ; rel="last", + ; rel="first" + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED3C:38B93F:1B68A83:36861E8:675AF65F + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4895' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '105' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/6412 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n ci: run the debugger suite only if necessary by P403n1x87 + \xB7 Pull Request #6412 \xB7 DataDog/dd-trace-py \xB7 GitHub\n\n\n\n + \ \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n ci: + run the debugger suite only if necessary\n #6412\n

\n
\n
\n\n
\n
\n \n + \ Merged\n\n
\n\n\n\n\n + \
\n P403n1x87\n + \ merged 7 commits into\n\n\n DataDog:1.x\n\nfrom\n\nP403n1x87:ci/debugger-suitespec\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Jul 25, + 2023\n\n\n
\n
\n\n\n \n\n\n\n
\n
\n
\n
\n + \
\n \n Merged\n\n + \
\n\n\n\n\n
\n + \

\n \n ci: run the debugger suite only if necessary\n \n + \ #6412\n

\n\n + \
\n P403n1x87\n merged 7 commits into\n\n\n DataDog:1.x\n\nfrom\n\nP403n1x87:ci/debugger-suitespec\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Jul 25, + 2023\n\n\n
\n
\n
\n + \
\n
\n
\n
\n
\n\n\n\n + \ \n + \ \n\n\n + \ \n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"P403n1x87\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \"@P403n1x87\"\n\n \n + \ P403n1x87\n \n\n \n\n \n\n commented\n\n\n + \ Jul + 20, 2023\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited by majorgreys\n + \ \n \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

We introduce the concept + of suitespec as a way of describing how sources affect test runs. We use it + to ensure that the debugger tests run only if sources that the suite depends + on are modified by the current commit.

\n

Suitespec Implementation + Details

\n

The suitespec solution is based on a manual + configuration of of test suites. To simplify the declaration of file patterns + for test suites, one can make use of components, which essentially + are a logic collection of patterns. Test suite can then be declared as a list + of components to reflect their dependencies on these logic parts, and to DRY + the declaration itself by avoiding repetitions.

\n

Notes

\n
    \n
  • When the script fails for any reason, tests are run.
  • \n
  • It + is important that path patterns are listed correctly, or some tests might + not run when they are in fact supposed to.
  • \n
  • Best effort to determine + the correct list of changed files via the GitHub REST API. When that fails, + we fall back to the less accurate git diff + against the target branch.
  • \n
\n

Checklist

\n
    \n
  • Change(s) + are motivated and described in the PR description.
  • \n
  • Testing strategy is described if automated tests are not included + in the PR.
  • \n
  • Risk is outlined + (performance impact, potential for breakage, maintainability, etc).
  • \n
  • Change is maintainable (easy to change, telemetry, documentation).
  • \n
  • Library release note guidelines are followed. If no release + note is required, add label changelog/no-changelog.
  • \n
  • Documentation is included (in-code, generated user docs, public corp docs).
  • \n
  • Backport labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Title + is accurate.
  • \n
  • No unnecessary + changes are introduced.
  • \n
  • Description + motivates each change.
  • \n
  • Avoids + breaking API changes unless absolutely necessary.
  • \n
  • Testing strategy adequately addresses listed risk(s).
  • \n
  • Change is maintainable (easy to change, telemetry, documentation).
  • \n
  • Release note makes sense to a user of the library.
  • \n
  • Reviewer has explicitly acknowledged and discussed the performance + implications of this PR as reported in the benchmarks PR comment.
  • \n
  • Backport labels are set in a manner that is consistent with + the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n \n
\n + \
\n \n \n\n
\n
\n\n + \ \n\n \"@P403n1x87\"\nP403n1x87\n\n\n\n\n added\n the \n\n changelog/no-changelog\n\n A changelog + entry is not required for this PR.\n label\n\n\n Jul 20, 2023\n\n
\n
\n\n\n\n\n
\n\n + \
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n riotfile.py\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 4 times, most recently\n from\n 8953a58 + \ to\n 575d15e + \ \n + \ Compare\n \n\n\n\n July 20, 2023 13:13 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n
\n + \ \n scripts/needs_testrun.py\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@emmettbutler\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Collaborator\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n emmettbutler\n + \ \n\n \n\n \n\n commented\n\n\n Jul 20, 2023\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n + \ \n
\n + \

I love this idea!

\n
\n
\n\n\n
\n\n + \ \n\n
\n
\n + \
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n + \
\n + \ \n \n
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n
\n\n

\n
\n \"@brettlangdon\"\n\n \n brettlangdon\n \n\n \n\n \n\n + \ left a comment\n\n\n\n\n \n
\n\n

\n
\n \n\n
\n
\n + \ \n
\n \n \n\n

Choose a reason for hiding this comment

\n\n + \

\n The reason will be displayed to describe this + comment to others. Learn more.\n + \

\n\n
\n \n \n
\n\n + \ \n
\n\n \n
\n

I know @gnufede was trying to get CI Visibility + running for this repo, if we go that route, we might be able to ITR ?

\n + \
\n
\n \n
\n\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n + \
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n
\n \n \n
\n
\n
\n + \ \n tests/.suitespec.json\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@P403n1x87\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n Author\n\n\n
\n\n

\n
\n \n\n \n + \ P403n1x87\n \n\n \n\n \n\n commented\n\n\n + \ Jul 20, 2023\n\n\n \n
\n\n + \

\n
\n\n\n
\n\n \n\n + \ \n \n \n \n + \ \n
\n
\n

I know @gnufede + was trying to get CI Visibility running for this repo, if we go that route, + we might be able to ITR ?

\n
\n

My understanding + is that ITR is a per-test rather than per-test-suite. So I see ITR improving + this even further rather than an alternative?

\n
\n
\n\n\n
\n\n + \ \n\n
\n
\n
\n \n \n
\n + \ emmettbutler reacted with thumbs up emoji\n + \
\n \n + \
\n
\n
\n
\n
\n + \
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 3 times, most recently\n from\n 713167a + \ to\n e8c3ecc + \ \n + \ Compare\n \n\n\n\n July 20, 2023 17:15 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@emmettbutler\"\n emmettbutler\n\n\n self-requested a review\n\n\n + \ July 20, 2023 21:23 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 20, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n
\"@P403n1x87\"\n P403n1x87\n\n\n dismissed\n emmettbutler\u2019s stale review\n\n\n + \ via\n \n 4e53e79\n + \ \n\n July + 21, 2023 09:41 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n e8c3ecc + \ to\n 4e53e79 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 09:41 \n + \ \n
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 21, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n
\n + \ \n .circleci/config.yml\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 3 times, most recently\n from\n d2671c5 + \ to\n 19b0da0 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 10:35 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n marked this pull request as + ready for review\n\n July + 21, 2023 10:41 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n a team\n\n as code owners\n\n\n + \ July 21, 2023 10:41 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n majorgreys, + \n jbertran, + \n brettlangdon, + \n emmettbutler + and \n a team\n\n\n\n July 21, 2023 10:41 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n af236d7 + \ to\n a4c0000 + \ \n + \ Compare\n \n\n\n\n July 21, 2023 15:26 \n + \ \n
\n
\n\n\n
\n\n\n
\n + \
\n
\n
\n \n \n
\n
\n + \
\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n 2 times, most recently\n from\n c50870c + \ to\n e812418 + \ \n + \ Compare\n \n\n\n\n July 24, 2023 12:52 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n
\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"P403n1x87\"\n + \
\n \n
\n + \
\n + \ \n P403n1x87\n + \ \n\n \n\n commented\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n \n \nShow resolved\n + \ \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n + \ \n
\n \n
\n
\"@brettlangdon\"\n brettlangdon\n\n\n dismissed\n emmettbutler\u2019s stale review\n\n\n + \ via\n \n cdb1444\n + \ \n\n July + 24, 2023 16:44 \n \n
\n
\n\n\n
\n\n + \
\n \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n reviewed\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
+ \ \n + \
\n + \
\n \n .circleci/config.templ.yml\n\n + \ \n Outdated\n \n \n \nShow + resolved\n \n \nHide resolved\n + \
\n
\n
\n + \ \n \n \n \n\n \n
\n
\n\n\n\n\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n brettlangdon + and \n emmettbutler\n\n\n\n + \ July 24, 2023 18:57 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n \n
\n \n
\n \"emmettbutler\"\n + \
\n \n
\n + \
\n + \ \n emmettbutler\n + \ \n\n \n\n previously approved these changes\n\n\n + \ \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n + \
\n + \ \n \n
\n
\n
+ \
\n
\n\n\n\n\n
\n + \ \n
\n
\n \n
\n \n
\n + \
P403n1x87\n \n\n added 5 commits\n + \ July 24, 2023 22:13
\n
+ \
\n
\n + \ \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 0d844de\n \n
\n
\n + \
\n
\n
We introduce the concept of suitespec as a way of describing
+        how\nsources affect test runs. We use it to ensure that the debugger\ntests
+        run only if sources that the suite depends on are modified\nby the current
+        commit.
\n
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 1ffab15\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n web + scraping FTW\n \n\n
\n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ a115763\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n add + doctests\n \n\n
\n\n
\n \n\n + \ \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 4d0fb2e\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \
\n \n use + dynamic config\n \n\n
\n\n
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 690a7b1\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \n
\n
\"@P403n1x87\"\n P403n1x87\n\n\n dismissed stale reviews from + emmettbutler + and brettlangdon\n\n\n + \ via\n \n 690a7b1\n + \ \n\n July + 24, 2023 21:17 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n force-pushed\n + \ the\n \n \n ci/debugger-suitespec\n\n\n + \ \n branch\n from\n 5f1daca + \ to\n 690a7b1 + \ \n + \ Compare\n \n\n\n\n July 24, 2023 21:17 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@P403n1x87\"\n P403n1x87\n\n\n requested review from\n emmettbutler + and \n brettlangdon\n\n\n\n + \ July 24, 2023 21:17 \n + \ \n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \"brettlangdon\"\n + \
\n \n
\n + \
\n + \ \n brettlangdon\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Jul + 24, 2023\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n\n\n + \
\n + \ \n
\n
\n \n
\n \n
\n + \
P403n1x87\n \n\n added 2 commits\n + \ July 25, 2023 09:06
\n
+ \
\n
\n + \ \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ f421ece\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@P403n1x87\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 3eacc26\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n + \ \n + \ \n\n + \
\n
\n\n\n \"@P403n1x87\"\n P403n1x87\n\n\n\n merged commit f441242\n into\n\n \n \n DataDog:1.x\n + \ \n\n\n Jul 25, 2023\n\n
\n
\n\n
\n\n
\n
\n \n \n\n
\n\n
\n
\n \"@Yun-Kim\"\nYun-Kim\n\n\n\n mentioned this pull request\n \n Jul 26, 2023\n + \ \n
\n\n\n\n\n \n
\n \n \n \n\n \n \n\n + \ \n \n \n \n\n\n 16 + tasks\n
\n
\n\n\n\n
\n
\n\n + \ \n
\n \n + \ \n + \ \n\n \n
\n \n Yun-Kim \n\n added a commit\n that referenced\n + \ this pull request\n\n \n + \ Jul + 26, 2023\n \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@Yun-Kim\"\n + \
\n
\n\n\n \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n\n \n + \
\n \n 43497d1\n \n
\n
\n + \
\n
\n
#6412
+        changed our circleci configuration setup to be dynamic, but this\ninadvertently
+        removed the `coverage` and `riot_run_latest` circleci\npipeline parameters
+        from the main `.circleci/config.yml` file, which\nbreaks our nightly 1.x coverage
+        pipeline runs. This PR re-adds those\nparameters back and re-enables coverage
+        reporting.\n\nNote that `datastreams`, `langchain`, `elasticsearch`,\n`integration-snapshot`
+        test suites are still failing on 1.x nightly\ncoverage runs and will need
+        to be fixed.\n\n## Checklist\n\n- [x] Change(s) are motivated and described
+        in the PR description.\n- [x] Testing strategy is described if automated tests
+        are not included\nin the PR.\n- [x] Risk is outlined (performance impact,
+        potential for breakage,\nmaintainability, etc).\n- [x] Change is maintainable
+        (easy to change, telemetry, documentation).\n- [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [x] Title is accurate.\n- [x] No unnecessary changes
+        are introduced.\n- [x] Description motivates each change.\n- [x] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [x] Testing strategy adequately addresses
+        listed risk(s).\n- [x] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [x] Release note makes sense to a user of the library.\n-
+        [x] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [x] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n \n
\n \n \n \n\n \n
\n + \ \n romainkomorndatadog + \n\n pushed a commit\n that referenced\n this pull request\n\n + \ \n Aug 8, 2023\n + \ \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@P403n1x87\"\n + \ \n \"@romainkomorndatadog\"\n + \
\n
\n\n\n
\n + \ \n ci: + run the debugger suite only if necessary (#6412)\n + \ \n\n \n + \ \n \n\n
\n\n + \
\n \n\n \n \n \n\n \n\n
\n\n
\n
\n\n \n
\n + \ \n 6838e4b\n \n
\n
\n + \
\n
\n
We introduce the concept of suitespec as a way of describing
+        how sources\naffect test runs. We use it to ensure that the debugger tests
+        run only\nif sources that the suite depends on are modified by the current
+        commit.\n\n## Suitespec Implementation Details\n\nThe suitespec solution is
+        based on a manual configuration of of test\nsuites. To simplify the declaration
+        of file patterns for test suites,\none can make use of _components_, which
+        essentially are a logic\ncollection of patterns. Test suite can then be declared
+        as a list of\ncomponents to reflect their dependencies on these logic parts,
+        and to\nDRY the declaration itself by avoiding repetitions.\n\n## Notes\n\n-
+        When the script fails for any reason, tests are run.\n- It is important that
+        path patterns are listed correctly, or some tests\nmight not run when they
+        are in fact supposed to.\n- Best effort to determine the correct list of changed
+        files via the\nGitHub REST API. When that fails, we fall back to the less
+        accurate `git\ndiff` against the target branch.\n\n## Checklist\n\n- [x] Change(s)
+        are motivated and described in the PR description.\n- [x] Testing strategy
+        is described if automated tests are not included\nin the PR.\n- [x] Risk is
+        outlined (performance impact, potential for breakage,\nmaintainability, etc).\n-
+        [x] Change is maintainable (easy to change, telemetry, documentation).\n-
+        [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [ ] Title is accurate.\n- [ ] No unnecessary changes
+        are introduced.\n- [ ] Description motivates each change.\n- [ ] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [ ] Testing strategy adequately addresses
+        listed risk(s).\n- [ ] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [ ] Release note makes sense to a user of the library.\n-
+        [ ] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [ ] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n \n
\n \n \n \n\n \n
\n + \ \n romainkomorndatadog + \n\n pushed a commit\n that referenced\n this pull request\n\n + \ \n Aug 8, 2023\n + \ \n
\n \n
\n + \
\n
\n \n
\n
\n \n \"@Yun-Kim\"\n + \ \n \"@romainkomorndatadog\"\n + \
\n
\n\n\n \n\n + \
\n \n\n \n \n \n\n \n\n
\n\n
\n
\n\n \n
\n + \ \n b38e5ce\n \n
\n
\n + \
\n
\n
#6412
+        changed our circleci configuration setup to be dynamic, but this\ninadvertently
+        removed the `coverage` and `riot_run_latest` circleci\npipeline parameters
+        from the main `.circleci/config.yml` file, which\nbreaks our nightly 1.x coverage
+        pipeline runs. This PR re-adds those\nparameters back and re-enables coverage
+        reporting.\n\nNote that `datastreams`, `langchain`, `elasticsearch`,\n`integration-snapshot`
+        test suites are still failing on 1.x nightly\ncoverage runs and will need
+        to be fixed.\n\n## Checklist\n\n- [x] Change(s) are motivated and described
+        in the PR description.\n- [x] Testing strategy is described if automated tests
+        are not included\nin the PR.\n- [x] Risk is outlined (performance impact,
+        potential for breakage,\nmaintainability, etc).\n- [x] Change is maintainable
+        (easy to change, telemetry, documentation).\n- [x] [Library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\nare
+        followed. If no release note is required, add label\n`changelog/no-changelog`.\n-
+        [x] Documentation is included (in-code, generated user docs, [public\ncorp
+        docs](https://github.com/DataDog/documentation/)).\n-
+        [x] Backport labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n##
+        Reviewer Checklist\n\n- [x] Title is accurate.\n- [x] No unnecessary changes
+        are introduced.\n- [x] Description motivates each change.\n- [x] Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges
+        unless absolutely necessary.\n- [x] Testing strategy adequately addresses
+        listed risk(s).\n- [x] Change is maintainable (easy to change, telemetry,
+        documentation).\n- [x] Release note makes sense to a user of the library.\n-
+        [x] Reviewer has explicitly acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment.\n- [x] Backport labels
+        are set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
\n + \
\n
\n\n
\n
\n
\n\n\n\n
\n\n\n\n \n
\n
\n \n
+ \
\n\n\n\n \n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n \n\n\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@brettlangdon\"\n \n brettlangdon\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n brettlangdon approved these changes\n\n + \

\n

\n \n\n \n \"@majorgreys\"\n \n majorgreys\n\n\n + \ Awaiting requested review from majorgreys\n\n + \ majorgreys is a code owner automatically + assigned from DataDog/apm-core-python\n\n \n

\n + \

\n \n\n \n \"@jbertran\"\n \n jbertran\n\n\n + \ Awaiting requested review from jbertran\n\n + \ jbertran was automatically assigned from + DataDog/apm-framework-integrations-reviewers-py\n\n \n

\n + \

\n \n\n \n \"@emmettbutler\"\n \n emmettbutler\n\n\n + \ Awaiting requested review from emmettbutler\n\n\n + \ \n

\n\n \n
\n\n
\n\n\n
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n \n\n changelog/no-changelog\n\n + \ A changelog entry is not required for + this PR.\n\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 4 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n\n\n\n\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:36 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=l3QKY0YBtVa6g6vTxtaD7V81b1rqVKbVC2TuUprbrsBeLyGMxD93o4PKJuuuX8DsRurIz%2BgmK%2Bu2SsrLsbekGNnDfnlY8nmv6JixFA0imSoyuXwZ1hoQQntsqmb%2BY5H2ZmdVcxjGdx4KfXsgsWYyampVlxVtj8kcqBXpQ1EmwL8bxCXFb1Ua2ljpQIrEF0vAkXxAKjJvD9Nkk%2BoV9Oq9FDdyOTS5F09seblwdhXqyPUiRtK%2F47XQlwOGT%2Bbx3gQZd0o0tqUnOHebKKHm1e8WeA%3D%3D--cOTu%2FzH%2Bqi016usb--raQjHHicfYwN6TAxmM%2B1hg%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.165126635.1734014562; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:42 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:42 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED48:2AD30D:249761D:33182B5:675AF662 + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=259.185408,conversation_content-fragment;desc="conversation_content + fragment";dur=1167.36918,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=278.203377,nginx;desc="NGINX";dur=1.232025,glb;desc="GLB";dur=3.090931 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/11534 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n fix(asm): add global states to ensure patching once [backport + 2.15] by christophe-papazian \xB7 Pull Request #11534 \xB7 DataDog/dd-trace-py + \xB7 GitHub\n\n\n\n \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n fix(asm): + add global states to ensure patching once [backport 2.15]\n #11534\n

\n
\n
\n\n + \
\n + \
\n + \ \n Merged\n\n + \
\n\n\n\n\n
\n gnufede\n merged 3 commits into\n\n\n 2.15\n\nfrom\n\nbackport-11522-to-2.15\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Nov 26, + 2024\n\n\n
\n
\n\n\n \n\n\n\n
\n
\n
\n
\n + \
\n \n Merged\n\n + \
\n\n\n\n\n
\n + \

\n \n fix(asm): add global states to ensure patching once [backport + 2.15]\n \n #11534\n

\n\n + \
\n gnufede\n merged 3 commits into\n\n\n 2.15\n\nfrom\n\nbackport-11522-to-2.15\n \n \n \n\n \n \n\n + \
\n
\n\n\n + \ Nov 26, + 2024\n\n\n
\n
\n
\n + \
\n
\n
\n
\n
\n\n\n\n + \ \n
\n
\n \n \n +74\n + \ \n \n \u221210\n + \ \n \n \n + \ \n \n
\n\n \n
\n\n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"christophe-papazian\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \"@christophe-papazian\"\n\n \n + \ christophe-papazian\n \n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited\n \n + \ \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

Backport 81824b8 + from #11522 to 2.15.

\n

Ensure common patches for SCA and Exploit Prevention are loaded..

\n

only once
\nonly if exploit prevention is active or sca is + active
\nChanges:

\n

factorize load_common_modules logic + in ddtrace.appsec
\nboolean state for patch_common_module and enable_iast_propagation + to ensure they are only called once.
\nensure it's loaded after one click + activation
\nensure it's properly loaded in unit tests if required
\nadd + some failsafe for iast in wrap_open for importerror
\nupdate an iast test + to reflect that common_modules is loaded in the test by default.
\nAPPSEC-55997

\n

Checklist

\n
    \n
  • PR author has checked that all the criteria below are met
  • \n
  • The + PR description includes an overview of the change
  • \n
  • The PR description + articulates the motivation for the change
  • \n
  • The change includes tests + OR the PR description describes a testing strategy
  • \n
  • The PR description + notes risks associated with the change, if any
  • \n
  • Newly-added code + is easy to change
  • \n
  • The change follows the library release note guidelines
  • \n
  • The change + includes or references documentation updates if necessary
  • \n
  • Backport + labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Reviewer + has checked that all the criteria below are met
  • \n
  • Title is accurate
  • \n
  • All + changes are related to the pull request's stated goal
  • \n
  • Avoids breaking + API changes
  • \n
  • Testing strategy adequately addresses + listed risks
  • \n
  • Newly-added code is easy to change
  • \n
  • Release + note makes sense to a user of the library
  • \n
  • If necessary, author has + acknowledged and discussed the performance implications of this PR as reported + in the benchmarks PR comment
  • \n
  • Backport labels are set in a manner + that is consistent with the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n + \ \n
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@christophe-papazian\"\n + \
\n
\n\n
\n \n + \ fix(asm): + add global states to ensure patching once (#11522)\n + \ \n\n \n + \ + \ \n
\n\n
\n \n\n + \ \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ cd59645\n \n
\n
\n + \
\n
\n
Ensure common patches for SCA and Exploit Prevention are loaded..\n-
+        only once\n- only if exploit prevention is active or sca is active\n\nChanges:\n-
+        factorize load_common_modules logic in ddtrace.appsec\n- boolean state for
+        patch_common_module and enable_iast_propagation to\nensure they are only called
+        once.\n- ensure it's loaded after one click activation\n- ensure it's properly
+        loaded in unit tests if required\n- add some failsafe for iast in wrap_open
+        for importerror\n- update an iast test to reflect that common_modules is loaded
+        in the\ntest by default.\n\nAPPSEC-55997\n\n- [x] PR author has checked that
+        all the criteria below are met\n- The PR description includes an overview
+        of the change\n- The PR description articulates the motivation for the change\n-
+        The change includes tests OR the PR description describes a testing\nstrategy\n-
+        The PR description notes risks associated with the change, if any\n- Newly-added
+        code is easy to change\n- The change follows the [library release note\nguidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)\n-
+        The change includes or references documentation updates if necessary\n- Backport
+        labels are set (if\n[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))\n\n-
+        [x] Reviewer has checked that all the criteria below are met\n- Title is accurate\n-
+        All changes are related to the pull request's stated goal\n- Avoids breaking\n[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)\nchanges\n-
+        Testing strategy adequately addresses listed risks\n- Newly-added code is
+        easy to change\n- Release note makes sense to a user of the library\n- If
+        necessary, author has acknowledged and discussed the performance\nimplications
+        of this PR as reported in the benchmarks PR comment\n- Backport labels are
+        set in a manner that is consistent with the\n[release branch maintenance\npolicy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)\n\n(cherry
+        picked from commit 81824b8)
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n marked + this pull request as ready for review\n\n November + 25, 2024 16:51 \n \n
\n
\n
\n + \ \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n requested + review from\n a team\n\n + \ as code owners\n\n\n + \ November 25, 2024 16:51 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@christophe-papazian\"\n christophe-papazian\n\n\n requested + review from\n gnufede + and \n emmettbutler\n\n\n\n + \ November 25, 2024 16:51 \n + \ \n
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@github-actions\"\n\n \n + \ \"GitHub\n \n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n github-actions\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

CODEOWNERS have + been resolved as:

\n
releasenotes/notes/exploit_prevention_patch_fix-1bdd7540e1d085d8.yaml
+        \  @DataDog/apm-python\nddtrace/_monkey.py                                                      @DataDog/apm-core-python\nddtrace/appsec/__init__.py
+        \                                             @DataDog/asm-python\nddtrace/appsec/_common_module_patches.py
+        \                               @DataDog/asm-python\nddtrace/appsec/_iast/__init__.py
+        \                                       @DataDog/asm-python\nddtrace/appsec/_remoteconfiguration.py
+        \                                 @DataDog/asm-python\ntests/appsec/integrations/test_flask_telemetry.py
+        \                      @DataDog/asm-python\ntests/utils.py                                                          @DataDog/python-guild\n
\n\n + \
\n
\n\n\n
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@datadog-dd-trace-py-rkomorn\"\n\n
\n\n\n + \
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n datadog-dd-trace-py-rkomorn\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Datadog Report

\n

Branch report: + backport-11522-to-2.15
\nCommit + report: c476a58
\nTest + service: dd-trace-py

\n

\u2705 + 0 Failed, 592 Passed, 694 Skipped, 19m 30.54s Total duration (15m 23.31s time + saved)

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"gnufede\"\n + \
\n \n
\n + \
\n + \ \n gnufede\n \n\n + \ \n\n approved these changes\n\n\n \n \n + \ Nov + 25, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n \n
\n + \ \n
\n + \ \n
\n
\n \"@gnufede\"\n gnufede\n\nenabled + auto-merge (squash)\n\n November + 25, 2024 17:32 \n \n
\n
\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@pr-commenter\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n pr-commenter\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Nov 25, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Benchmarks

\n

Benchmark execution + time: 2024-11-26 21:13:50

\n

Comparing candidate commit + c476a58 + in PR branch backport-11522-to-2.15 with + baseline commit b462888 + in branch 2.15.

\n

Found + 0 performance improvements and 0 performance regressions! Performance is the + same for 371 metrics, 53 unstable metrics.

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"erikayasuda\"\n + \
\n \n
\n + \
\n + \ \n erikayasuda\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Nov + 26, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n + \ \n
\n
\n \n
\n \n
\n + \
christophe-papazian\n \nand others\n + \ added 2 commits\n November + 26, 2024 18:39
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@christophe-papazian\"\n + \
\n
\n\n \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ 3ac9ef8\n \n
\n
\n + \
\n
\n\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@erikayasuda\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ c476a58\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n + \ \n + \ \n\n + \
\n
\n\n \n + \ \"@gnufede\"\n gnufede\n\n\n\n + \ merged commit 2d6800f\n into\n\n \n \n 2.15\n \n\n\n Nov 26, 2024\n\n
\n 584 checks passed\n
\n\n
\n + \ \n \n + \ \n \n + \ \n\n + \ \n
\n
\n
\n\n
\n\n + \
\n \n
\n \n
\n
\"@gnufede\"\n gnufede\n\n\n + \ \n deleted the\n \n + \ \n backport-11522-to-2.15\n \n branch\n\n + \ November 26, 2024 21:16 \n + \ \n
\n
\n\n\n
\n\n\n\n\n\n \n
\n
\n \n
+ \
\n\n\n\n
\n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n
\n\n
\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@erikayasuda\"\n \n erikayasuda\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n erikayasuda approved these changes\n\n + \

\n

\n \n\n \n \"@gnufede\"\n \n gnufede\n\n\n\n \n + \ \n \n + \ \n\n \n \n gnufede approved these changes\n\n + \

\n

\n \n\n \n \"@emmettbutler\"\n \n emmettbutler\n\n\n + \ Awaiting requested review from emmettbutler\n\n + \ emmettbutler is a code owner automatically + assigned from DataDog/apm-python\n\n \n

\n\n \n
\n\n
\n\n\n + \
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n None yet\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 3 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n
\n\n\n\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:37 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=lcakJUZEMJACUB16KjyZXhnaDJ7Lf%2FYHyuliVSpr%2BebjFOJkqrrGVy310bXG4sCIwd5suyAhSq1ar47KgrE92K2xy%2FyLkV0kyOGj2HTHZLBE0AoalTEwk%2FtwXY2eTPd4xUPomg0vtlqQrrYnQNHrj9IxaNsg225S2Xxjw2F05HFwCLbhj4Tdo2o8BBOYJeV2WH8GGT4bJ6XT0VeQRP3trJrZhY9WOPmlbyZ0k%2Biokd%2By3Tr6Fld4rQ3BKKJ6Nq%2FEfMuSc4M5FDuoXJzxMyyAGg%3D%3D--VglcwRFwLrbj7fn0--vsOdxQkYekEyYwll%2BqS%2B1A%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.1476856324.1734014562; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:42 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:42 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED4A:356904:24669B2:32ED588:675AF662 + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=450.768495,conversation_content-fragment;desc="conversation_content + fragment";dur=576.513283,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=305.288275,nginx;desc="NGINX";dur=1.093278,glb;desc="GLB";dur=4.679312 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Connection: + - close + Host: + - github.com + method: GET + uri: https://github.com/DataDog/dd-trace-py/pull/11690 + response: + body: + string: "\n\n\n\n\n\n\n\n\n\n\n\n \n \n + \ \n \n \n \n + \ \n + \ \n\n + \ \n\n \n\n \n \n \n \n \n\n\n \n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n\n\n\n\n ci: store fake DD_API_KEY as a secret by brettlangdon \xB7 + Pull Request #11690 \xB7 DataDog/dd-trace-py \xB7 GitHub\n\n\n\n \n \n \n\n \n \n\n\n + \ \n\n\n \n\n\n \n \n\n \n \n\n + \ \n\n\n\n \n\n \n\n\n\n\n \n\n \n\n \n\n + \ \n\n \n\n \n\n \n \n \n\n \n \n \n\n\n\n\n \n\n\n\n + \ \n\n\n \n \n \n \n\n \n\n \n + \ \n\n + \ \n\n\n\n \n\n \n\n\n \n\n \n\n \n \n + \ \n\n\n\n\n\n \n\n + \ \n\n \n
\n \n\n\n
\n Skip to content\n\n + \ \n \n + \ \n \n \n\n\n\n\n \n \n + \
\n\n\n\n\n\n + \ \n\n \n\n \n\n\n
\n

Navigation Menu

\n\n \n\n + \
\n
\n
\n + \ \n
\n\n \n + \ \n + \ \n\n + \ \n\n
\n \n Sign in\n \n
\n
\n\n\n + \
\n
\n + \ \n\n
\n \n\n\n\n \n \n
\n \n \n\n + \
\n Search + or jump to...\n
\n + \ \n\n + \
\n \n\n \n\n \n
\n \n + \

Search + code, repositories, users, issues, pull requests...

\n
\n \n
+ \
\n
\n \n
\n \n \n \n \n \n\n \n
\n
\n
\n
\n + \ \n
\n + \
\n Clear\n + \ \n\n + \
\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n + \
\n \n + \
\n + \
\n
\n\n \n + \
\n
\n\n
\n
\n
\n \n
\n + \ \n\n \n
\n + \
\n
\n + \

\n Provide + feedback\n

\n \n
\n
\n + \ \n
\n
\n + \ \n
\n \n + \
\n

We read every piece of feedback, and take your input very + seriously.

\n \n \n + \ \n
\n
\n + \ \n
\n\n \n \n\n + \ \n
\n
\n + \
\n

\n Saved searches\n

\n + \

Use + saved searches to filter your results more quickly

\n
\n
\n \n + \
\n
\n \n
\n \n + \
\n\n \n\n
\n + \
\n
\n\n
\n + \
\n \n
\n + \
\n
\n\n\n
\n \n Sign in\n \n + \
\n\n \n Sign + up\n \n \n
\n + \
\n
\n \n\n\n \n \n\n + \
\n\n\n\n\n\n\n\n\n + \
\n\n\n + \ \n\n\n + \ \n
\n\n\n + \ \n\n\n\n\n\n\n \n
\n
\n \n \n\n\n\n + \ \n \n\n \n\n\n\n\n\n\n \n
\n\n
\n\n + \
\n \n
\n + \ \n \n\n + \ \n \n + \ \n DataDog\n + \ \n /\n + \ \n dd-trace-py\n \n\n Public\n
\n\n\n + \
\n\n
\n \n\n + \
\n
\n\n
\n
\n\n\n \n\n + \
\n\n \n\n\n\n\n
\n \n\n\n\n \n \n
\n \n\n
\n \n \n \n\n
\n
\n
\n\n \n
\n \n \n New issue\n \n \n + \
\n
\n \n \n\n
\n\n
\n

\n Have a question + about this project? Sign up for a free GitHub account to open an + issue and contact its maintainers and the community.\n

\n\n \n\n

By + clicking “Sign up for GitHub”, you agree to our terms of service + and\n privacy statement. We\u2019ll occasionally send you + account related emails.

\n\n

\n + \ Already on GitHub?\n Sign + in\n to your account\n

\n
\n\n
\n
\n
\n + \ \n + \
\n\n

\n ci: + store fake DD_API_KEY as a secret\n #11690\n + \

\n
\n
\n\n
\n
\n \n + Open\n\n
\n\n\n\n\n
\n brettlangdon\n\n wants to merge\n 1\n + \ commit into\n\n\n main\n\n + \
\n
\n + \ \n base:\n + \ main\n \n + \ \n \n + \ \n
\n
\n + \
\n Choose + a base branch\n \n
\n\n + \ \n
\n + \ \n
\n\n \n \n\n
\n \n\n \n\n \n\n\n
\n
\n \n + \ \n \n \n Loading\n\n + \
\n
\n\n \n\n\n \n\n + \
\n\n \n
\n + \
\n
\n
\n\n \n + \
\n
\n\n
\n \n
\n\nfrom\n\nbrettlangdon-patch-3\n \n \n \n\n \n \n\n + \
\n
\n\n\n\n + \ \n \n\n\n\n\n\n\n\n\n \n \n + \
\n\n\n + \
\n\n
\n
\n\n\n \n\n\n\n
\n
\n + \
\n + \
\n + \
\n \n Open\n\n
\n\n\n\n\n + \
\n

\n \n + \ ci: store fake DD_API_KEY as a secret\n \n #11690\n

\n\n
\n brettlangdon\n\n + \ wants to merge\n 1\n + \ commit into\n\n\n main\n\nfrom\n\nbrettlangdon-patch-3\n \n \n \n\n \n \n\n + \
\n
\n\n\n\n\n + \
\n
\n
\n
\n + \
\n
\n
\n
\n\n\n\n \n
\n
\n \n \n +2\n \n \n \u22122\n \n \n + \ \n \n \n + \
\n\n \n
\n\n\n\n
\n + \
\n

Conversation

\n + \ \n \n\n\n \n\n
\n\n
\n \"brettlangdon\"\n + \ \n \n
\n + \
\n
\n
\n
\n \n \n \n\n \n\n\n \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n
\n\n

\n
\n \"@brettlangdon\"\n\n \n brettlangdon\n \n\n \n\n \n\n + \ commented\n\n\n Dec 12, 2024\n\n\n \n + \ \n\n
\n + \ \n
\n \n edited\n \n + \ \n \n \n\n
\n
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n
\n + \
\n \n \n + \
\n

Checklist

\n
    \n
  • PR author + has checked that all the criteria below are met
  • \n
  • The PR description + includes an overview of the change
  • \n
  • The PR description articulates + the motivation for the change
  • \n
  • The change includes tests OR the PR + description describes a testing strategy
  • \n
  • The PR description notes + risks associated with the change, if any
  • \n
  • Newly-added code is easy + to change
  • \n
  • The change follows the library release note guidelines
  • \n
  • The change + includes or references documentation updates if necessary
  • \n
  • Backport + labels are set (if applicable)
  • \n
\n

Reviewer Checklist

\n
    \n
  • Reviewer + has checked that all the criteria below are met
  • \n
  • Title is accurate
  • \n
  • All + changes are related to the pull request's stated goal
  • \n
  • Avoids breaking + API changes
  • \n
  • Testing strategy adequately addresses + listed risks
  • \n
  • Newly-added code is easy to change
  • \n
  • Release + note makes sense to a user of the library
  • \n
  • If necessary, author has + acknowledged and discussed the performance implications of this PR as reported + in the benchmarks PR comment
  • \n
  • Backport labels are set in a manner + that is consistent with the release branch maintenance policy
  • \n
\n
\n + \
\n \n
\n\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n\n + \
\n
\n
\n \n
\n
\n + \ \n
\n
\n
\n + \
\n\n
\n
\n
\n\n\n \n\n \n
\n\n\n
\n + \ \n
\n
\n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n \n \"@brettlangdon\"\n
\n
\n\n + \ \n\n + \
\n \n\n \n \n \n\n \n\n \n\n\n + \
\n\n
\n + \
\n + \ \n\n + \ \n \n \n\n \n\n
\n \n
\n\n
\n + \
\n
\n\n
\n \n + \ a6675d3\n \n
\n
\n + \
\n
\n\n
\n
\n
\n\n\n
\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \
\n\n \n\n \"@brettlangdon\"\nbrettlangdon\n\n\n\n\n added\n the \n\n changelog/no-changelog\n\n A changelog + entry is not required for this PR.\n label\n\n\n Dec 12, 2024\n\n
\n
\n\n\n + \
\n \n
\n \n
\n + \
\"@brettlangdon\"\n brettlangdon\n\n\n requested review from\n + \ a team\n\n as code owners\n\n\n + \ December 12, 2024 13:39 \n + \ \n
\n
\n
\n \n
\n \n
\n + \
\"@brettlangdon\"\n brettlangdon\n\n\n requested review from\n + \ avara1986 + and \n erikayasuda\n\n\n\n + \ December 12, 2024 13:39 \n + \ \n
\n
\n\n\n
\n\n
\n \n \n
\n\n
\n + \ \"@github-actions\"\n\n \n + \ \"GitHub\n \n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n + \ Contributor\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n github-actions\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

CODEOWNERS have + been resolved as:

\n
.github/workflows/system-tests.yml
+        \                                     @DataDog/python-guild @DataDog/apm-core-python\n
\n\n + \
\n
\n\n\n
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n \n
\n \"romainkomorndatadog\"\n + \
\n \n
\n + \
\n + \ \n romainkomorndatadog\n + \ \n\n \n\n approved these changes\n\n\n \n \n + \ Dec + 12, 2024\n \n \n \n + \
\n\n \n
\n
\n\n
\n \n \n
\n
\n
\n
\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@datadog-dd-trace-py-rkomorn\"\n\n
\n\n\n + \
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n datadog-dd-trace-py-rkomorn\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Datadog Report

\n

Branch report: + brettlangdon-patch-3
\nCommit + report: a6675d3
\nTest + service: dd-trace-py

\n

\u2705 + 0 Failed, 55 Passed, 1413 Skipped, 1m 29.81s Total duration (35m 20.17s time + saved)

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@brettlangdon\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n + \ \n Member\n\n\n \n\n Author\n\n\n + \
\n\n

\n
\n + \ \n\n \n brettlangdon\n + \ \n\n \n\n \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

/merge

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n \n
\n\n
\n + \ \"@dd-devflow\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n dd-devflow\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n \n\n
\n \n
\n + \ \n edited\n \n \n \n + \ \n\n
\n + \
\n \n \n \n + \ \n \n + \ \n Loading\n\n \n \n + \
\n
\n\n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \ \n

Devflow running: /merge

\n

View all feedbacks in Devflow UI.

\n
\n

2024-12-12 13:54:30 UTC \u2139\uFE0F MergeQueue: + waiting for PR to be ready

\n

This merge request is not + mergeable yet, because of pending checks/missing approvals. It will be added + to the queue as soon as checks pass and/or get approvals.
\nNote: + if you pushed new commits since the last approval, you may need additional + approval.
\nYou can remove it from the waiting list with /remove + command.

\n

Use /merge -c + to cancel this operation!

\n
\n

2024-12-12 + 14:26:14 UTC \u2139\uFE0F MergeQueue: merge request + added to the queue

\n

The median merge time in main + is 34m.

\n

Use /merge -c + to cancel this operation!

\n
\n

\u23F3 + command still in progress ...

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \
\n\n \n\n \"@dd-devflow\"\ndd-devflow\nbot\n\n\n\n added\n the \n\n mergequeue-status: waiting\n\n label\n\n\n Dec 12, 2024\n\n
\n
\n\n\n\n\n
\n\n + \
\n \n \n
\n\n
\n + \ \"@pr-commenter\"\n\n
\n\n\n
\n\n
\n + \
\n + \
\n + \
\n + \ \n + \ \n \n\n \n\n\n + \ \n \n \n \n Copy + link\n\n
\n
\n + \
\n
\n\n
\n \n\n\n\n + \ \n\n
\n\n

\n + \
\n \n\n \n pr-commenter\n + \ bot\n\n \n\n + \ \n\n commented\n\n\n Dec 12, 2024\n\n\n + \ \n
\n\n

\n
\n\n\n
\n\n + \ \n\n \n \n + \ \n \n \n
\n + \

Benchmarks

\n

Benchmark execution + time: 2024-12-12 14:24:20

\n

Comparing candidate commit + a6675d3 + in PR branch brettlangdon-patch-3 with + baseline commit 385d8e0 + in branch main.

\n

Found + 0 performance improvements and 0 performance regressions! Performance is the + same for 394 metrics, 2 unstable metrics.

\n
\n
\n\n\n + \
\n\n \n\n
\n + \
\n
\n \n \n
\n
\n \n
\n
\n
\n
\n + \
\n
\n\n
\n \n + \

\n \n + \ \n \n \n\n

\n \n\n\n + \
\n
\n\n
\n\n\n
\n\n\n
\n + \ \n
\n
\n \n + \ \n\n
\n + \ \n
\n\n\n\n\n
\n\n\n\n\n\n + \ \n
\n
\n \n
+ \
\n\n\n\n
\n\n
\n + \
\n
\n \n Sign up for free\n to join + this conversation on GitHub.\n Already have an account?\n Sign + in to comment\n\n\n \n
\n\n
\n
\n
\n\n
\n + \
\n
\n\n\n \n
\n \n
\n \n
\n Reviewers\n
\n\n \n\n\n + \

\n \n\n \n \"@romainkomorndatadog\"\n \n romainkomorndatadog\n\n\n\n + \ \n + \ \n \n + \ \n\n \n \n romainkomorndatadog approved these changes\n\n + \

\n

\n \n\n \n \"@avara1986\"\n \n avara1986\n\n\n + \ Awaiting requested review from avara1986\n\n + \ avara1986 is a code owner automatically + assigned from DataDog/python-guild\n\n \n

\n

\n \n\n \n \"@erikayasuda\"\n \n erikayasuda\n\n\n + \ Awaiting requested review from erikayasuda\n\n + \ erikayasuda is a code owner automatically + assigned from DataDog/apm-core-python\n\n \n

\n\n + \ \n
\n\n
\n\n\n
\n
\n\n \n
\n Assignees\n + \
\n\n\n \n\n + \ No one assigned\n\n\n\n
\n\n\n \n\n \n\n\n
\n Labels\n
\n\n\n
\n \n\n changelog/no-changelog\n\n + \ A changelog entry is not required for + this PR.\n \n\n mergequeue-status: + in_progress\n\n\n
\n\n
\n\n\n \n\n
\n
\n
\n Projects\n + \
\n\n
\n
\n\n None yet\n\n\n\n
\n\n\n + \ \n
\n
\n \n
\n Milestone\n + \
\n\n No milestone\n\n
\n\n\n \n \n \n
\n
\n \n
\n \n
\n Development\n + \
\n\n\n \n\n

Successfully merging this pull request may + close these issues.

\n\n\n \n\n
+ \
\n
\n
\n\n \n \n\n + \ \n\n \n
\n + \
\n
\n 2 participants\n
\n \n
\n
\n\n\n\n + \ \n\n \n\n\n\n\n \n\n
\n\n\n
\n \n \n \n + \ \n\n\n + \ \n\n\n \n\n\n\n\n \n \n\n + \ \n\n
\n

Footer

\n\n \n\n\n
\n
\n \n \n \n\n\n + \ \n © 2024 GitHub, Inc.\n \n
\n\n + \ \n
\n
\n\n\n\n\n \n\n\n \n\n + \ \n\n
\n + \
\n
\n
\n\n \n\n\n\n\n\n \n\n
\n + \
\n \n\n\n" + headers: + Accept-Ranges: + - bytes + Cache-Control: + - no-cache + Content-Security-Policy: + - 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/ + github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/; + connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com + raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com + github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com + *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com + objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com + proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com + wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ + productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ + productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ + productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ + productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ + productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ + productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ + productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ + productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ + productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ + productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com + github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com + wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com + api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src + github.githubassets.com; form-action ''self'' github.com gist.github.com copilot-workspace.githubnext.com + objects-origin.githubusercontent.com; frame-ancestors ''none''; frame-src + viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src + ''self'' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com + identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com + github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ + user-images.githubusercontent.com/ private-user-images.githubusercontent.com + opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com + customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com + *.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/ + secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com + github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src + github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com; + upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/ + github.com/assets/ gist.github.com/assets-cdn/worker/' + Content-Type: + - text/html; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:38 GMT + Referrer-Policy: + - no-referrer-when-downgrade + Server: + - GitHub.com + Set-Cookie: + - _gh_sess=SRp9AZpG%2B9PjbOI2DwrEGHPVoSPO1RQxFghqLR7KL1Fy058969XVQivgCdTFTsevR18tNXoZ%2FKyRkxCnOi2HhrErMbXOcrBwL5FA5%2FuR4HL8V1NhpBn75oTzynU53VGcHD6m7%2BIlieWYdCDurncYFhjKC%2FyJMwrbWCv8a%2BqwOdUGnXDfrkHq9if6PsYS6W3SV3HjEy72OBGtOU%2FpHCZOngO5mPkK52xmJZd5cZuqoLImJBzkm8LUbVPQjcLWKerz3McWy5a71T9kSEnN3Z0www%3D%3D--5w9odkPS8zIP4Vqv--zkwZym8AQTQ3LMLwY5hRjw%3D%3D; + Path=/; HttpOnly; Secure; SameSite=Lax + - _octo=GH1.1.172292125.1734014562; Path=/; Domain=github.com; Expires=Fri, + 12 Dec 2025 14:42:42 GMT; Secure; SameSite=Lax + - logged_in=no; Path=/; Domain=github.com; Expires=Fri, 12 Dec 2025 14:42:42 + GMT; HttpOnly; Secure; SameSite=Lax + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Transfer-Encoding: + - chunked + Vary: + - X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, + X-Requested-With + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Request-Id: + - ED4B:321365:23B1314:320365D:675AF662 + X-XSS-Protection: + - '0' + connection: + - close + server-timing: + - pull_request_layout-fragment;desc="pull_request_layout fragment";dur=412.175919,conversation_content-fragment;desc="conversation_content + fragment";dur=448.910543,conversation_sidebar-fragment;desc="conversation_sidebar + fragment";dur=302.334653,nginx;desc="NGINX";dur=1.331055,glb;desc="GLB";dur=3.067062 + x-voltron-version: + - 69a2227 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/6388/files?page=1 + response: + body: + string: '[{"sha":"1325b0864ebc6d4c40970f698018ac2524fe4e33","filename":"ddtrace/debugging/_expressions.py","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/ddtrace%2Fdebugging%2F_expressions.py","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/ddtrace%2Fdebugging%2F_expressions.py","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/ddtrace%2Fdebugging%2F_expressions.py?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -292,8 +292,8 @@ def _compile_operation(ast):\n \n def _compile_literal(ast):\n # + type: (DDASTType) -> Optional[List[Instr]]\n- # literal => | + true | false | \"string\"\n- if not isinstance(ast, (str, int, float, bool)):\n+ # + literal => | true | false | \"string\" | null\n+ if not (isinstance(ast, + (str, int, float, bool)) or ast is None):\n return None\n \n return + [Instr(\"LOAD_CONST\", ast)]"},{"sha":"b4517ad79f67a2b362360ae8e7e0b0b3fa2e4ea8","filename":"releasenotes/notes/fix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","status":"added","additions":4,"deletions":0,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/releasenotes%2Fnotes%2Ffix-debugger-expressions-none-literal-30f3328d2e386f40.yaml?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -0,0 +1,4 @@\n+---\n+fixes:\n+ - |\n+ dynamic instrumentation: handle + null literal in conditions and expressions."},{"sha":"3c4d96fe66b871238c02651af82d43a1ad8085c3","filename":"tests/debugging/test_expressions.py","status":"modified","additions":1,"deletions":0,"changes":1,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/2eb060881fdd94f4f717ae19549b598317b74d30/tests%2Fdebugging%2Ftest_expressions.py","raw_url":"https://github.com/DataDog/dd-trace-py/raw/2eb060881fdd94f4f717ae19549b598317b74d30/tests%2Fdebugging%2Ftest_expressions.py","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/tests%2Fdebugging%2Ftest_expressions.py?ref=2eb060881fdd94f4f717ae19549b598317b74d30","patch":"@@ + -72,6 +72,7 @@ def __getitem__(self, name):\n # Test argument predicates + and operations\n ({\"contains\": [{\"ref\": \"payload\"}, \"hello\"]}, + {\"payload\": \"hello world\"}, True),\n ({\"eq\": [{\"ref\": \"hits\"}, + True]}, {\"hits\": True}, True),\n+ ({\"eq\": [{\"ref\": \"hits\"}, + None]}, {\"hits\": None}, True),\n ({\"substring\": [{\"ref\": \"payload\"}, + 4, 7]}, {\"payload\": \"hello world\"}, \"hello world\"[4:7]),\n ({\"any\": + [{\"ref\": \"collection\"}, {\"isEmpty\": {\"ref\": \"@it\"}}]}, {\"collection\": + [\"foo\", \"bar\", \"\"]}, True),\n ({\"startsWith\": [{\"ref\": \"local_string\"}, + \"hello\"]}, {\"local_string\": \"hello world!\"}, True),"}]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '3264' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:43 GMT + ETag: + - '"85a10accfc7f3330efa4961171936a0e3ea39a94e59a1811461b17a9a610bdb4"' + Last-Modified: + - Sun, 08 Dec 2024 16:19:43 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED4D:111D81:1B766DC:3695A0A:675AF662 + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4894' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '106' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/6388/files?page=2 + response: + body: + string: '[]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:43 GMT + ETag: + - '"4acd3c336ca9625e24fba0a2ea9cad06cf4693ace7e76d92c8a9a05f03c7b0cd"' + Last-Modified: + - Sun, 08 Dec 2024 16:19:43 GMT + Link: + - ; rel="prev", + ; rel="last", + ; rel="first" + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED4E:1C31C4:1AEFA31:3584AFC:675AF663 + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4893' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '107' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/11690/files?page=1 + response: + body: + string: '[{"sha":"ce795db4fe24584e0a3c105f6f130071b1292cbe","filename":".github/workflows/system-tests.yml","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/DataDog/dd-trace-py/blob/a6675d3799af44382bd5b677c56a94843a6433aa/.github%2Fworkflows%2Fsystem-tests.yml","raw_url":"https://github.com/DataDog/dd-trace-py/raw/a6675d3799af44382bd5b677c56a94843a6433aa/.github%2Fworkflows%2Fsystem-tests.yml","contents_url":"https://api.github.com/repos/DataDog/dd-trace-py/contents/.github%2Fworkflows%2Fsystem-tests.yml?ref=a6675d3799af44382bd5b677c56a94843a6433aa","patch":"@@ + -54,7 +54,7 @@ jobs:\n # system-tests requires an API_KEY, but it does + not have to be a valid key, as long as we don''t run a scenario\n # + that make assertion on backend data. Using a fake key allow to run system + tests on PR originating from forks.\n # If ever it''s needed, a valid + key exists in the repo, using ${{ secrets.DD_API_KEY }}\n- DD_API_KEY: + 1234567890abcdef1234567890abcdef\n+ DD_API_KEY: ${{ secrets.FAKE_DD_API_KEY + }}\n CMAKE_BUILD_PARALLEL_LEVEL: 12\n SYSTEM_TESTS_AWS_ACCESS_KEY_ID: + ${{ secrets.IDM_AWS_ACCESS_KEY_ID }}\n SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: + ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }}\n@@ -106,7 +106,7 @@ jobs:\n # + system-tests requires an API_KEY, but it does not have to be a valid key, + as long as we don''t run a scenario\n # that make assertion on backend + data. Using a fake key allow to run system tests on PR originating from forks.\n # + If ever it''s needed, a valid key exists in the repo, using ${{ secrets.DD_API_KEY + }}\n- DD_API_KEY: 1234567890abcdef1234567890abcdef\n+ DD_API_KEY: + ${{ secrets.FAKE_DD_API_KEY }}\n CMAKE_BUILD_PARALLEL_LEVEL: 12\n SYSTEM_TESTS_AWS_ACCESS_KEY_ID: + ${{ secrets.IDM_AWS_ACCESS_KEY_ID }}\n SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: + ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }}"}]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '1930' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:43 GMT + ETag: + - '"e91026bdc9aa216ff163739444e03dfcf4e719131166fd717d6e5a7eafbd54fe"' + Last-Modified: + - Thu, 12 Dec 2024 14:26:20 GMT + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED50:122321:1C06DEB:37B36A8:675AF663 + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4892' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '108' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/vnd.github+json + Connection: + - close + Host: + - api.github.com + method: GET + uri: https://api.github.com/repos/datadog/dd-trace-py/pulls/11690/files?page=2 + response: + body: + string: '[]' + headers: + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, + X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, + X-GitHub-Request-Id, Deprecation, Sunset + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Thu, 12 Dec 2024 14:42:44 GMT + ETag: + - '"4acd3c336ca9625e24fba0a2ea9cad06cf4693ace7e76d92c8a9a05f03c7b0cd"' + Last-Modified: + - Thu, 12 Dec 2024 14:26:20 GMT + Link: + - ; rel="prev", + ; rel="last", + ; rel="first" + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Server: + - github.com + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With + X-Accepted-OAuth-Scopes: + - '' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-GitHub-Media-Type: + - github.v3; format=json + X-GitHub-Request-Id: + - ED52:23893A:1B6422E:3678EE8:675AF664 + X-OAuth-Scopes: + - delete:packages, gist, read:org, read:packages, repo, workflow + X-RateLimit-Limit: + - '5000' + X-RateLimit-Remaining: + - '4891' + X-RateLimit-Reset: + - '1734015073' + X-RateLimit-Resource: + - core + X-RateLimit-Used: + - '109' + X-XSS-Protection: + - '0' + connection: + - close + x-github-api-version-selected: + - '2022-11-28' + x-oauth-client-id: + - 178c6fc778ccc68e1d6a + status: + code: 200 + message: OK +version: 1 From 710ed97c4dba053bf75e9b651e339ae2551a892f Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 12 Dec 2024 14:23:26 -0500 Subject: [PATCH 293/372] chore(profiling): ddup.upload() optionally gets tracer object (#11695) profiling tests that check for span ids are flaky. I wonder whether sharing tracer instance across tests causes that problem. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../internal/datadog/profiling/ddup/_ddup.pyi | 3 ++- .../internal/datadog/profiling/ddup/_ddup.pyx | 7 ++++--- ddtrace/profiling/profiler.py | 1 + ddtrace/profiling/scheduler.py | 6 +++++- tests/profiling_v2/collector/test_asyncio.py | 5 ++--- tests/profiling_v2/collector/test_stack.py | 21 +++++++++---------- .../profiling_v2/collector/test_threading.py | 17 +++++++-------- 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi index 552e377df0b..78351e93b91 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi @@ -3,6 +3,7 @@ from typing import Optional from typing import Union from .._types import StringType from ddtrace._trace.span import Span +from ddtrace._trace.tracer import Tracer def config( env: StringType, @@ -16,7 +17,7 @@ def config( enable_code_provenance: Optional[bool], ) -> None: ... def start() -> None: ... -def upload() -> None: ... +def upload(tracer: Optional[Tracer]) -> None: ... class SampleHandle: def push_cputime(self, value: int, count: int) -> None: ... diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx index b3f9b264890..9c590c796d7 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx @@ -20,6 +20,7 @@ from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.packages import get_distributions from ddtrace.internal.runtime import get_runtime_id from ddtrace._trace.span import Span +from ddtrace._trace.tracer import Tracer ctypedef void (*func_ptr_t)(string_view) @@ -396,16 +397,16 @@ def _get_endpoint(tracer)-> str: return endpoint -def upload() -> None: +def upload(tracer: Optional[Tracer] = ddtrace.tracer) -> None: call_func_with_str(ddup_set_runtime_id, get_runtime_id()) - processor = ddtrace.tracer._endpoint_call_counter_span_processor + processor = tracer._endpoint_call_counter_span_processor endpoint_counts, endpoint_to_span_ids = processor.reset() call_ddup_profile_set_endpoints(endpoint_to_span_ids) call_ddup_profile_add_endpoint_counts(endpoint_counts) - endpoint = _get_endpoint(ddtrace.tracer) + endpoint = _get_endpoint(tracer) call_func_with_str(ddup_config_url, endpoint) with nogil: diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index fa4bdb79a3e..9903cc29108 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -311,6 +311,7 @@ def start_collector(collector_class: Type) -> None: recorder=r, exporters=exporters, before_flush=self._collectors_snapshot, + tracer=self.tracer, ) def _collectors_snapshot(self): diff --git a/ddtrace/profiling/scheduler.py b/ddtrace/profiling/scheduler.py index 9f286f8688b..e8aafe7a63b 100644 --- a/ddtrace/profiling/scheduler.py +++ b/ddtrace/profiling/scheduler.py @@ -7,6 +7,8 @@ from typing import Optional from typing import Sequence # noqa F401 +import ddtrace +from ddtrace._trace.tracer import Tracer from ddtrace.internal import compat from ddtrace.internal import periodic from ddtrace.internal.datadog.profiling import ddup @@ -30,6 +32,7 @@ def __init__( recorder: Optional[Recorder] = None, exporters: Optional[List[Exporter]] = None, before_flush: Optional[Callable] = None, + tracer: Optional[Tracer] = ddtrace.tracer, interval: float = config.upload_interval, ): super(Scheduler, self).__init__(interval=interval) @@ -38,6 +41,7 @@ def __init__( self.before_flush: Optional[Callable] = before_flush self._configured_interval: float = self.interval self._last_export: int = 0 # Overridden in _start_service + self._tracer = tracer self._export_libdd_enabled: bool = config.export.libdd_enabled def _start_service(self): @@ -59,7 +63,7 @@ def flush(self): LOG.error("Scheduler before_flush hook failed", exc_info=True) if self._export_libdd_enabled: - ddup.upload() + ddup.upload(self._tracer) # These are only used by the Python uploader, but set them here to keep logs/etc # consistent for now diff --git a/tests/profiling_v2/collector/test_asyncio.py b/tests/profiling_v2/collector/test_asyncio.py index c29ff7fe92c..f0c9bb625d9 100644 --- a/tests/profiling_v2/collector/test_asyncio.py +++ b/tests/profiling_v2/collector/test_asyncio.py @@ -7,7 +7,6 @@ import pytest from ddtrace import ext -from ddtrace import tracer from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import asyncio as collector_asyncio from tests.profiling.collector import pprof_utils @@ -85,7 +84,7 @@ async def test_asyncio_lock_events(self): ], ) - async def test_asyncio_lock_events_tracer(self): + async def test_asyncio_lock_events_tracer(self, tracer): tracer._endpoint_call_counter_span_processor.enable() resource = str(uuid.uuid4()) span_type = ext.SpanTypes.WEB @@ -103,7 +102,7 @@ async def test_asyncio_lock_events_tracer(self): lock_ctx = asyncio.Lock() # !CREATE! test_asyncio_lock_events_tracer_3 async with lock_ctx: # !ACQUIRE! !RELEASE! test_asyncio_lock_events_tracer_3 pass - ddup.upload() + ddup.upload(tracer=tracer) linenos_1 = get_lock_linenos("test_asyncio_lock_events_tracer_1") linenos_2 = get_lock_linenos("test_asyncio_lock_events_tracer_2") diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 5d9007248bc..af13a1ea237 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -9,7 +9,6 @@ import pytest from ddtrace import ext -from ddtrace import tracer from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import stack from ddtrace.settings.profiling import config @@ -82,7 +81,7 @@ def foo(): @pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_span(stack_v2_enabled, tmp_path): +def test_push_span(stack_v2_enabled, tmp_path, tracer): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: pytest.skip("stack_v2 is not supported on Python 3.7") @@ -111,7 +110,7 @@ def test_push_span(stack_v2_enabled, tmp_path): local_root_span_id = span._local_root.span_id for _ in range(10): time.sleep(0.1) - ddup.upload() + ddup.upload(tracer=tracer) profile = pprof_utils.parse_profile(output_filename) samples = pprof_utils.get_samples_with_label_key(profile, "span id") @@ -129,7 +128,7 @@ def test_push_span(stack_v2_enabled, tmp_path): ) -def test_push_span_unregister_thread(tmp_path, monkeypatch): +def test_push_span_unregister_thread(tmp_path, monkeypatch, tracer): if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") @@ -166,7 +165,7 @@ def target_fun(): t.start() t.join() thread_id = t.ident - ddup.upload() + ddup.upload(tracer=tracer) profile = pprof_utils.parse_profile(output_filename) samples = pprof_utils.get_samples_with_label_key(profile, "span id") @@ -187,7 +186,7 @@ def target_fun(): @pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_non_web_span(stack_v2_enabled, tmp_path): +def test_push_non_web_span(stack_v2_enabled, tmp_path, tracer): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: pytest.skip("stack_v2 is not supported on Python 3.7") @@ -216,7 +215,7 @@ def test_push_non_web_span(stack_v2_enabled, tmp_path): local_root_span_id = span._local_root.span_id for _ in range(10): time.sleep(0.1) - ddup.upload() + ddup.upload(tracer=tracer) profile = pprof_utils.parse_profile(output_filename) samples = pprof_utils.get_samples_with_label_key(profile, "span id") @@ -235,7 +234,7 @@ def test_push_non_web_span(stack_v2_enabled, tmp_path): @pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_span_none_span_type(stack_v2_enabled, tmp_path): +def test_push_span_none_span_type(stack_v2_enabled, tmp_path, tracer): # Test for https://github.com/DataDog/dd-trace-py/issues/11141 if sys.version_info[:2] == (3, 7) and stack_v2_enabled: pytest.skip("stack_v2 is not supported on Python 3.7") @@ -266,7 +265,7 @@ def test_push_span_none_span_type(stack_v2_enabled, tmp_path): local_root_span_id = span._local_root.span_id for _ in range(10): time.sleep(0.1) - ddup.upload() + ddup.upload(tracer=tracer) profile = pprof_utils.parse_profile(output_filename) samples = pprof_utils.get_samples_with_label_key(profile, "span id") @@ -398,7 +397,7 @@ def target_fun(): @pytest.mark.skipif(not stack.FEATURES["stack-exceptions"], reason="Stack exceptions are not supported") @pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_exception_collection_trace(stack_v2_enabled, tmp_path): +def test_exception_collection_trace(stack_v2_enabled, tmp_path, tracer): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: pytest.skip("stack_v2 is not supported on Python 3.7") @@ -419,7 +418,7 @@ def test_exception_collection_trace(stack_v2_enabled, tmp_path): except Exception: time.sleep(1) - ddup.upload() + ddup.upload(tracer=tracer) profile = pprof_utils.parse_profile(output_filename) samples = pprof_utils.get_samples_with_label_key(profile, "exception type") diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index bb55e67522f..12b84fc9970 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -8,7 +8,6 @@ import pytest from ddtrace import ext -from ddtrace import tracer from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling.collector import threading as collector_threading from tests.profiling.collector import pprof_utils @@ -356,7 +355,7 @@ def lockfunc(self): ], ) - def test_lock_events_tracer(self): + def test_lock_events_tracer(self, tracer): tracer._endpoint_call_counter_span_processor.enable() resource = str(uuid.uuid4()) span_type = ext.SpanTypes.WEB @@ -375,7 +374,7 @@ def test_lock_events_tracer(self): span_id = t.span_id lock2.release() # !RELEASE! test_lock_events_tracer_2 - ddup.upload() + ddup.upload(tracer=tracer) linenos1 = get_lock_linenos("test_lock_events_tracer_1") linenos2 = get_lock_linenos("test_lock_events_tracer_2") @@ -419,7 +418,7 @@ def test_lock_events_tracer(self): ], ) - def test_lock_events_tracer_non_web(self): + def test_lock_events_tracer_non_web(self, tracer): tracer._endpoint_call_counter_span_processor.enable() resource = str(uuid.uuid4()) span_type = ext.SpanTypes.SQL @@ -435,7 +434,7 @@ def test_lock_events_tracer_non_web(self): span_id = t.span_id lock2.release() # !RELEASE! test_lock_events_tracer_non_web - ddup.upload() + ddup.upload(tracer=tracer) linenos2 = get_lock_linenos("test_lock_events_tracer_non_web") @@ -463,7 +462,7 @@ def test_lock_events_tracer_non_web(self): ], ) - def test_lock_events_tracer_late_finish(self): + def test_lock_events_tracer_late_finish(self, tracer): tracer._endpoint_call_counter_span_processor.enable() resource = str(uuid.uuid4()) span_type = ext.SpanTypes.WEB @@ -482,7 +481,7 @@ def test_lock_events_tracer_late_finish(self): lock2.release() # !RELEASE! test_lock_events_tracer_late_finish_2 span.resource = resource span.finish() - ddup.upload() + ddup.upload(tracer=tracer) linenos1 = get_lock_linenos("test_lock_events_tracer_late_finish_1") linenos2 = get_lock_linenos("test_lock_events_tracer_late_finish_2") @@ -520,7 +519,7 @@ def test_lock_events_tracer_late_finish(self): ], ) - def test_resource_not_collected(self): + def test_resource_not_collected(self, tracer): tracer._endpoint_call_counter_span_processor.enable() resource = str(uuid.uuid4()) span_type = ext.SpanTypes.WEB @@ -539,7 +538,7 @@ def test_resource_not_collected(self): lock1.release() # !RELEASE! test_resource_not_collected_1 span_id = t.span_id lock2.release() # !RELEASE! test_resource_not_collected_2 - ddup.upload() + ddup.upload(tracer=tracer) linenos1 = get_lock_linenos("test_resource_not_collected_1") linenos2 = get_lock_linenos("test_resource_not_collected_2") From b87c4dd04831770911fc164c20b836fa73a079ef Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 12 Dec 2024 15:36:57 -0500 Subject: [PATCH 294/372] ci: store fake DD_API_KEY as a secret (#11690) --- .github/workflows/system-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 06604dc811c..ce795db4fe2 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -54,7 +54,7 @@ jobs: # system-tests requires an API_KEY, but it does not have to be a valid key, as long as we don't run a scenario # that make assertion on backend data. Using a fake key allow to run system tests on PR originating from forks. # If ever it's needed, a valid key exists in the repo, using ${{ secrets.DD_API_KEY }} - DD_API_KEY: 1234567890abcdef1234567890abcdef + DD_API_KEY: ${{ secrets.FAKE_DD_API_KEY }} CMAKE_BUILD_PARALLEL_LEVEL: 12 SYSTEM_TESTS_AWS_ACCESS_KEY_ID: ${{ secrets.IDM_AWS_ACCESS_KEY_ID }} SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }} @@ -106,7 +106,7 @@ jobs: # system-tests requires an API_KEY, but it does not have to be a valid key, as long as we don't run a scenario # that make assertion on backend data. Using a fake key allow to run system tests on PR originating from forks. # If ever it's needed, a valid key exists in the repo, using ${{ secrets.DD_API_KEY }} - DD_API_KEY: 1234567890abcdef1234567890abcdef + DD_API_KEY: ${{ secrets.FAKE_DD_API_KEY }} CMAKE_BUILD_PARALLEL_LEVEL: 12 SYSTEM_TESTS_AWS_ACCESS_KEY_ID: ${{ secrets.IDM_AWS_ACCESS_KEY_ID }} SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: ${{ secrets.IDM_AWS_SECRET_ACCESS_KEY }} From d364f1bb6e5b0287d2a5350cfb3329e4ba1e93c3 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 12 Dec 2024 15:37:16 -0500 Subject: [PATCH 295/372] ci: fix flaky aiohttp test failure (#11698) Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- tests/contrib/aiohttp/test_request.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/contrib/aiohttp/test_request.py b/tests/contrib/aiohttp/test_request.py index d32da71a927..cde0f311521 100644 --- a/tests/contrib/aiohttp/test_request.py +++ b/tests/contrib/aiohttp/test_request.py @@ -4,8 +4,6 @@ from ddtrace import config from ddtrace.contrib.aiohttp.middlewares import trace_app -from ddtrace.contrib.aiohttp.patch import patch -from ddtrace.contrib.aiohttp.patch import unpatch from tests.utils import assert_is_measured from tests.utils import override_global_config @@ -76,9 +74,7 @@ async def test_user_specified_service(tracer, aiohttp_client, loop): When a service name is specified by the user The aiohttp integration should use it as the service name """ - unpatch() with override_global_config(dict(service="mysvc")): - patch() app = setup_app() trace_app(app, tracer) client = await aiohttp_client(app) From 68bff3aaf1b53e64677d61746adab41fd7529a01 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Thu, 12 Dec 2024 15:44:44 -0500 Subject: [PATCH 296/372] chore(ci): enable quality gates (#11710) Enable Quality Gates to prevent new flaky tests from being merged into main or release branches. Example branches: https://github.com/DataDog/dd-trace-py/tree/erikayasuda/qg-simple-delayed-fix https://github.com/DataDog/dd-trace-py/tree/erikayasuda/qg-simple-quick-fix ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- .gitlab-ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b05126541d2..4105e2d5eb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - package - tests-gen - tests-trigger + - quality-gate - shared-pipeline - benchmarks - macrobenchmarks @@ -87,3 +88,17 @@ deploy_to_di_backend:manual: UPSTREAM_COMMIT_AUTHOR: $CI_COMMIT_AUTHOR UPSTREAM_TAG: $CI_COMMIT_TAG UPSTREAM_PACKAGE_JOB: build + +check_new_flaky_tests: + stage: quality-gate + extends: .testrunner + script: + - curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci + - export DD_SITE=datadoghq.com + - export DD_API_KEY=$(aws ssm get-parameter --region us-east-1 --name ci.${CI_PROJECT_NAME}.dd-api-key-qualitygate --with-decryption --query "Parameter.Value" --out text) + - export DD_APP_KEY=$(aws ssm get-parameter --region us-east-1 --name ci.${CI_PROJECT_NAME}.dd-app-key-qualitygate --with-decryption --query "Parameter.Value" --out text) + - datadog-ci gate evaluate + except: + - main + - '[0-9].[0-9]*' + - 'mq-working-branch**' \ No newline at end of file From ac24ade35253f04947c20f8996842dd0aa5983ff Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:21:23 -0500 Subject: [PATCH 297/372] chore(docs): add vertexai docs (#11713) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/integrations.rst | 7 +++++++ docs/spelling_wordlist.txt | 1 + 2 files changed, 8 insertions(+) diff --git a/docs/integrations.rst b/docs/integrations.rst index d07fbe33e45..04a94007626 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -478,6 +478,13 @@ urllib3 .. automodule:: ddtrace.contrib.urllib3 +.. _vertexai: + +vertexai +^^^^^^^^^^^^^^^^^^^ +.. automodule:: ddtrace.contrib.vertexai + + .. _vertica: Vertica diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index d3c185a9360..6f0bb1afa71 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -266,6 +266,7 @@ username uvicorn vendored versioned +vertexai vertica w3c websocket From a37aa20ed22f489ce2d433f997927150b7a2a890 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:04:48 -0500 Subject: [PATCH 298/372] fix(llmobs): do not ignore global patch env vars (#11662) Fixes #11639. This PR fixes an issue where `LLMObs.enable()` ignored/overrode global patch env vars including `DD_TRACE__ENABLED` and `DD_PATCH_MODULES`. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_llmobs.py | 17 +++++- ...global-patch-configs-a2adc4803f55b142.yaml | 5 ++ tests/llmobs/test_llmobs_service.py | 60 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-llmobs-do-not-ignore-global-patch-configs-a2adc4803f55b142.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index a3ac9501319..808cee89e0f 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -23,6 +23,7 @@ from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.llmobs._constants import ANNOTATIONS_CONTEXT_ID from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES @@ -347,8 +348,20 @@ def flush(cls) -> None: @staticmethod def _patch_integrations() -> None: - """Patch LLM integrations.""" - patch(**{integration: True for integration in SUPPORTED_LLMOBS_INTEGRATIONS.values()}) # type: ignore[arg-type] + """ + Patch LLM integrations. Ensure that we do not ignore DD_TRACE__ENABLED or DD_PATCH_MODULES settings. + """ + integrations_to_patch = {integration: True for integration in SUPPORTED_LLMOBS_INTEGRATIONS.values()} + for module, _ in integrations_to_patch.items(): + env_var = "DD_TRACE_%s_ENABLED" % module.upper() + if env_var in os.environ: + integrations_to_patch[module] = asbool(os.environ[env_var]) + dd_patch_modules = os.getenv("DD_PATCH_MODULES") + dd_patch_modules_to_str = parse_tags_str(dd_patch_modules) + integrations_to_patch.update( + {k: asbool(v) for k, v in dd_patch_modules_to_str.items() if k in SUPPORTED_LLMOBS_INTEGRATIONS.values()} + ) + patch(**integrations_to_patch) # type: ignore[arg-type] log.debug("Patched LLM integrations: %s", list(SUPPORTED_LLMOBS_INTEGRATIONS.values())) @classmethod diff --git a/releasenotes/notes/fix-llmobs-do-not-ignore-global-patch-configs-a2adc4803f55b142.yaml b/releasenotes/notes/fix-llmobs-do-not-ignore-global-patch-configs-a2adc4803f55b142.yaml new file mode 100644 index 00000000000..b080742d74a --- /dev/null +++ b/releasenotes/notes/fix-llmobs-do-not-ignore-global-patch-configs-a2adc4803f55b142.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + LLM Observability: This fix resolves an issue where ``LLMObs.enable()`` ignored global patch configurations, specifically + the ``DD_TRACE__ENABLED`` and ``DD_PATCH_MODULES`` environment variables. diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 160023f5df7..5808ed01513 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -31,6 +31,7 @@ from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING from ddtrace.llmobs._constants import TAGS +from ddtrace.llmobs._llmobs import SUPPORTED_LLMOBS_INTEGRATIONS from ddtrace.llmobs._llmobs import LLMObsTraceProcessor from ddtrace.llmobs.utils import Prompt from tests.llmobs._utils import _expected_llmobs_eval_metric_event @@ -144,6 +145,65 @@ def test_service_enable_already_enabled(mock_logs): mock_logs.debug.assert_has_calls([mock.call("%s already enabled", "LLMObs")]) +@mock.patch("ddtrace.llmobs._llmobs.patch") +def test_service_enable_patches_llmobs_integrations(mock_tracer_patch): + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + llmobs_service.enable() + mock_tracer_patch.assert_called_once() + kwargs = mock_tracer_patch.call_args[1] + for module in SUPPORTED_LLMOBS_INTEGRATIONS.values(): + assert kwargs[module] is True + llmobs_service.disable() + + +@mock.patch("ddtrace.llmobs._llmobs.patch") +def test_service_enable_does_not_override_global_patch_modules(mock_tracer_patch, monkeypatch): + monkeypatch.setenv("DD_PATCH_MODULES", "openai:false") + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + llmobs_service.enable() + mock_tracer_patch.assert_called_once() + kwargs = mock_tracer_patch.call_args[1] + for module in SUPPORTED_LLMOBS_INTEGRATIONS.values(): + if module == "openai": + assert kwargs[module] is False + continue + assert kwargs[module] is True + llmobs_service.disable() + + +@mock.patch("ddtrace.llmobs._llmobs.patch") +def test_service_enable_does_not_override_integration_enabled_env_vars(mock_tracer_patch, monkeypatch): + monkeypatch.setenv("DD_TRACE_OPENAI_ENABLED", "false") + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + llmobs_service.enable() + mock_tracer_patch.assert_called_once() + kwargs = mock_tracer_patch.call_args[1] + for module in SUPPORTED_LLMOBS_INTEGRATIONS.values(): + if module == "openai": + assert kwargs[module] is False + continue + assert kwargs[module] is True + llmobs_service.disable() + + +@mock.patch("ddtrace.llmobs._llmobs.patch") +def test_service_enable_does_not_override_global_patch_config(mock_tracer_patch, monkeypatch): + """Test that _patch_integrations() ensures `DD_PATCH_MODULES` overrides `DD_TRACE__ENABLED`.""" + monkeypatch.setenv("DD_TRACE_OPENAI_ENABLED", "true") + monkeypatch.setenv("DD_TRACE_ANTHROPIC_ENABLED", "false") + monkeypatch.setenv("DD_PATCH_MODULES", "openai:false") + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + llmobs_service.enable() + mock_tracer_patch.assert_called_once() + kwargs = mock_tracer_patch.call_args[1] + for module in SUPPORTED_LLMOBS_INTEGRATIONS.values(): + if module in ("openai", "anthropic"): + assert kwargs[module] is False + continue + assert kwargs[module] is True + llmobs_service.disable() + + def test_start_span_while_disabled_logs_warning(LLMObs, mock_logs): LLMObs.disable() _ = LLMObs.llm(model_name="test_model", name="test_llm_call", model_provider="test_provider") From e4742671776a9e09a29846a976c5804b08d252b1 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:34:35 -0500 Subject: [PATCH 299/372] chore(llmobs): use span store instead of temporary tags (#11543) This PR performs some cleanup refactors on the LLM Obs SDK and associated integrations. Specifically regarding the data stored, which includes LLMObs span metadata/metrics/tags/IO: - Stop storing these as temporary span tags and instead use the span store field, which allows arbitrary key value pairs but is not submitted to Datadog. This removes the potential for temporary tags to be not extracted and still submitted as a APM span tag. - Stop attempting `safe_json()` (i.e. `json.dumps()`) to store the above data, which is an expensive operation that adds up with the number of separate calls, and instead just store the raw values of the stored objects in the store field, and only call `safe_json()` "once" at payload encoding time. Things to look out for: - Previously we were calling `safe_json()` every time to store data as string span tags. One danger includes errors during span processing due to wrong types (expect string, likely receive a dictionary/object from the span store field) - By avoiding any jsonify processing before encode time, a small edge case appeared from the LLMObs SDK decorator function which auto-annotates non-LLM spans with input function argument maps. In Python 3.8, the `bind_partial().arguments` call used to extract the function arguments returns an OrderedDict (otherwise returns a regular Dict() in Python >= 3.9, which broke some tests as we were simply casting to a string when storing the input/output value). I added a fix to cast the `bind_partial().arguments` object to a dict to avoid this issue coming up. ## Next Steps This is a great first step, but there are still tons of performance improvements we can make to our encoding/writing. The most notable is that we call `json.dumps()` on span events more than once (to calculate the payload size before adding to the buffer). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_integrations/anthropic.py | 27 +- ddtrace/llmobs/_integrations/bedrock.py | 31 +- ddtrace/llmobs/_integrations/gemini.py | 27 +- ddtrace/llmobs/_integrations/langchain.py | 132 +++++---- ddtrace/llmobs/_integrations/openai.py | 57 ++-- ddtrace/llmobs/_integrations/vertexai.py | 30 +- ddtrace/llmobs/_llmobs.py | 49 ++-- ddtrace/llmobs/_trace_processor.py | 74 ++--- ddtrace/llmobs/_utils.py | 12 +- ddtrace/llmobs/_writer.py | 14 +- ddtrace/llmobs/decorators.py | 12 +- .../anthropic/test_anthropic_llmobs.py | 32 --- tests/contrib/openai/test_openai_llmobs.py | 32 --- tests/llmobs/_utils.py | 52 +++- tests/llmobs/test_llmobs_decorators.py | 8 +- tests/llmobs/test_llmobs_service.py | 271 ++++++------------ .../test_llmobs_span_agentless_writer.py | 28 +- tests/llmobs/test_llmobs_span_encoder.py | 72 +++++ tests/llmobs/test_llmobs_trace_processor.py | 98 ++++--- 19 files changed, 495 insertions(+), 563 deletions(-) create mode 100644 tests/llmobs/test_llmobs_span_encoder.py diff --git a/ddtrace/llmobs/_integrations/anthropic.py b/ddtrace/llmobs/_integrations/anthropic.py index 0747d68e77b..dfb39c0f7e9 100644 --- a/ddtrace/llmobs/_integrations/anthropic.py +++ b/ddtrace/llmobs/_integrations/anthropic.py @@ -19,7 +19,6 @@ from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration from ddtrace.llmobs._utils import _get_attr -from ddtrace.llmobs._utils import safe_json log = get_logger(__name__) @@ -66,21 +65,21 @@ def _llmobs_set_tags( system_prompt = kwargs.get("system") input_messages = self._extract_input_message(messages, system_prompt) - span.set_tag_str(SPAN_KIND, "llm") - span.set_tag_str(MODEL_NAME, span.get_tag("anthropic.request.model") or "") - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) - span.set_tag_str(METADATA, safe_json(parameters)) - span.set_tag_str(MODEL_PROVIDER, "anthropic") - - if span.error or response is None: - span.set_tag_str(OUTPUT_MESSAGES, json.dumps([{"content": ""}])) - else: + output_messages = [{"content": ""}] + if not span.error and response is not None: output_messages = self._extract_output_message(response) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) - usage = self._get_llmobs_metrics_tags(span) - if usage: - span.set_tag_str(METRICS, safe_json(usage)) + span._set_ctx_items( + { + SPAN_KIND: "llm", + MODEL_NAME: span.get_tag("anthropic.request.model") or "", + MODEL_PROVIDER: "anthropic", + INPUT_MESSAGES: input_messages, + METADATA: parameters, + OUTPUT_MESSAGES: output_messages, + METRICS: self._get_llmobs_metrics_tags(span), + } + ) def _extract_input_message(self, messages, system_prompt=None): """Extract input messages from the stored prompt. diff --git a/ddtrace/llmobs/_integrations/bedrock.py b/ddtrace/llmobs/_integrations/bedrock.py index 78798ae4f98..bf8b020ebea 100644 --- a/ddtrace/llmobs/_integrations/bedrock.py +++ b/ddtrace/llmobs/_integrations/bedrock.py @@ -19,7 +19,6 @@ from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations import BaseLLMIntegration from ddtrace.llmobs._utils import _get_llmobs_parent_id -from ddtrace.llmobs._utils import safe_json log = get_logger(__name__) @@ -37,9 +36,9 @@ def _llmobs_set_tags( operation: str = "", ) -> None: """Extract prompt/response tags from a completion and set them as temporary "_ml_obs.*" tags.""" - if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: + if span._get_ctx_item(PROPAGATED_PARENT_ID_KEY) is None: parent_id = _get_llmobs_parent_id(span) or "undefined" - span.set_tag(PARENT_ID_KEY, parent_id) + span._set_ctx_item(PARENT_ID_KEY, parent_id) parameters = {} if span.get_tag("bedrock.request.temperature"): parameters["temperature"] = float(span.get_tag("bedrock.request.temperature") or 0.0) @@ -48,20 +47,20 @@ def _llmobs_set_tags( prompt = kwargs.get("prompt", "") input_messages = self._extract_input_message(prompt) - - span.set_tag_str(SPAN_KIND, "llm") - span.set_tag_str(MODEL_NAME, span.get_tag("bedrock.request.model") or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag("bedrock.request.model_provider") or "") - - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) - span.set_tag_str(METADATA, safe_json(parameters)) - if span.error or response is None: - span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) - else: + output_messages = [{"content": ""}] + if not span.error and response is not None: output_messages = self._extract_output_message(response) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) - metrics = self._llmobs_metrics(span, response) - span.set_tag_str(METRICS, safe_json(metrics)) + span._set_ctx_items( + { + SPAN_KIND: "llm", + MODEL_NAME: span.get_tag("bedrock.request.model") or "", + MODEL_PROVIDER: span.get_tag("bedrock.request.model_provider") or "", + INPUT_MESSAGES: input_messages, + METADATA: parameters, + METRICS: self._llmobs_metrics(span, response), + OUTPUT_MESSAGES: output_messages, + } + ) @staticmethod def _llmobs_metrics(span: Span, response: Optional[Dict[str, Any]]) -> Dict[str, Any]: diff --git a/ddtrace/llmobs/_integrations/gemini.py b/ddtrace/llmobs/_integrations/gemini.py index f1a4730812f..491187475f0 100644 --- a/ddtrace/llmobs/_integrations/gemini.py +++ b/ddtrace/llmobs/_integrations/gemini.py @@ -19,7 +19,6 @@ from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model from ddtrace.llmobs._integrations.utils import llmobs_get_metadata_google from ddtrace.llmobs._utils import _get_attr -from ddtrace.llmobs._utils import safe_json class GeminiIntegration(BaseLLMIntegration): @@ -41,28 +40,28 @@ def _llmobs_set_tags( response: Optional[Any] = None, operation: str = "", ) -> None: - span.set_tag_str(SPAN_KIND, "llm") - span.set_tag_str(MODEL_NAME, span.get_tag("google_generativeai.request.model") or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag("google_generativeai.request.provider") or "") - instance = kwargs.get("instance", None) metadata = llmobs_get_metadata_google(kwargs, instance) - span.set_tag_str(METADATA, safe_json(metadata)) system_instruction = get_system_instructions_from_google_model(instance) input_contents = get_argument_value(args, kwargs, 0, "contents") input_messages = self._extract_input_message(input_contents, system_instruction) - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) - if span.error or response is None: - span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) - else: + output_messages = [{"content": ""}] + if not span.error and response is not None: output_messages = self._extract_output_message(response) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) - usage = get_llmobs_metrics_tags_google("google_generativeai", span) - if usage: - span.set_tag_str(METRICS, safe_json(usage)) + span._set_ctx_items( + { + SPAN_KIND: "llm", + MODEL_NAME: span.get_tag("google_generativeai.request.model") or "", + MODEL_PROVIDER: span.get_tag("google_generativeai.request.provider") or "", + METADATA: metadata, + INPUT_MESSAGES: input_messages, + OUTPUT_MESSAGES: output_messages, + METRICS: get_llmobs_metrics_tags_google("google_generativeai", span), + } + ) def _extract_input_message(self, contents, system_instruction=None): messages = [] diff --git a/ddtrace/llmobs/_integrations/langchain.py b/ddtrace/llmobs/_integrations/langchain.py index 2128458253d..1fce3d11804 100644 --- a/ddtrace/llmobs/_integrations/langchain.py +++ b/ddtrace/llmobs/_integrations/langchain.py @@ -28,7 +28,6 @@ from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration -from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.utils import Document @@ -130,15 +129,11 @@ def _llmobs_set_metadata(self, span: Span, model_provider: Optional[str] = None) if max_tokens is not None and max_tokens != "None": metadata["max_tokens"] = int(max_tokens) if metadata: - span.set_tag_str(METADATA, safe_json(metadata)) + span._set_ctx_item(METADATA, metadata) def _llmobs_set_tags_from_llm( self, span: Span, args: List[Any], kwargs: Dict[str, Any], completions: Any, is_workflow: bool = False ) -> None: - span.set_tag_str(SPAN_KIND, "workflow" if is_workflow else "llm") - span.set_tag_str(MODEL_NAME, span.get_tag(MODEL) or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag(PROVIDER) or "") - input_tag_key = INPUT_VALUE if is_workflow else INPUT_MESSAGES output_tag_key = OUTPUT_VALUE if is_workflow else OUTPUT_MESSAGES stream = span.get_tag("langchain.request.stream") @@ -146,21 +141,28 @@ def _llmobs_set_tags_from_llm( prompts = get_argument_value(args, kwargs, 0, "input" if stream else "prompts") if isinstance(prompts, str) or not isinstance(prompts, list): prompts = [prompts] - if stream: # chat and llm take the same input types for streamed calls - span.set_tag_str(input_tag_key, safe_json(self._handle_stream_input_messages(prompts))) + input_messages = self._handle_stream_input_messages(prompts) else: - span.set_tag_str(input_tag_key, safe_json([{"content": str(prompt)} for prompt in prompts])) + input_messages = [{"content": str(prompt)} for prompt in prompts] + + span._set_ctx_items( + { + SPAN_KIND: "workflow" if is_workflow else "llm", + MODEL_NAME: span.get_tag(MODEL) or "", + MODEL_PROVIDER: span.get_tag(PROVIDER) or "", + input_tag_key: input_messages, + } + ) if span.error: - span.set_tag_str(output_tag_key, safe_json([{"content": ""}])) + span._set_ctx_item(output_tag_key, [{"content": ""}]) return if stream: message_content = [{"content": completions}] # single completion for streams else: message_content = [{"content": completion[0].text} for completion in completions.generations] - if not is_workflow: input_tokens, output_tokens, total_tokens = self.check_token_usage_chat_or_llm_result(completions) if total_tokens > 0: @@ -169,8 +171,8 @@ def _llmobs_set_tags_from_llm( OUTPUT_TOKENS_METRIC_KEY: output_tokens, TOTAL_TOKENS_METRIC_KEY: total_tokens, } - span.set_tag_str(METRICS, safe_json(metrics)) - span.set_tag_str(output_tag_key, safe_json(message_content)) + span._set_ctx_item(METRICS, metrics) + span._set_ctx_item(output_tag_key, message_content) def _llmobs_set_tags_from_chat_model( self, @@ -180,10 +182,13 @@ def _llmobs_set_tags_from_chat_model( chat_completions: Any, is_workflow: bool = False, ) -> None: - span.set_tag_str(SPAN_KIND, "workflow" if is_workflow else "llm") - span.set_tag_str(MODEL_NAME, span.get_tag(MODEL) or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag(PROVIDER) or "") - + span._set_ctx_items( + { + SPAN_KIND: "workflow" if is_workflow else "llm", + MODEL_NAME: span.get_tag(MODEL) or "", + MODEL_PROVIDER: span.get_tag(PROVIDER) or "", + } + ) input_tag_key = INPUT_VALUE if is_workflow else INPUT_MESSAGES output_tag_key = OUTPUT_VALUE if is_workflow else OUTPUT_MESSAGES stream = span.get_tag("langchain.request.stream") @@ -203,17 +208,17 @@ def _llmobs_set_tags_from_chat_model( ) role = getattr(message, "role", ROLE_MAPPING.get(message.type, "")) input_messages.append({"content": str(content), "role": str(role)}) - span.set_tag_str(input_tag_key, safe_json(input_messages)) + span._set_ctx_item(input_tag_key, input_messages) if span.error: - span.set_tag_str(output_tag_key, json.dumps([{"content": ""}])) + span._set_ctx_item(output_tag_key, [{"content": ""}]) return output_messages = [] if stream: content = chat_completions.content role = chat_completions.__class__.__name__.replace("MessageChunk", "").lower() # AIMessageChunk --> ai - span.set_tag_str(output_tag_key, safe_json([{"content": content, "role": ROLE_MAPPING.get(role, "")}])) + span._set_ctx_item(output_tag_key, [{"content": content, "role": ROLE_MAPPING.get(role, "")}]) return input_tokens, output_tokens, total_tokens = 0, 0, 0 @@ -249,7 +254,7 @@ def _llmobs_set_tags_from_chat_model( output_tokens = sum(v["output_tokens"] for v in tokens_per_choice_run_id.values()) total_tokens = sum(v["total_tokens"] for v in tokens_per_choice_run_id.values()) - span.set_tag_str(output_tag_key, safe_json(output_messages)) + span._set_ctx_item(output_tag_key, output_messages) if not is_workflow and total_tokens > 0: metrics = { @@ -257,7 +262,7 @@ def _llmobs_set_tags_from_chat_model( OUTPUT_TOKENS_METRIC_KEY: output_tokens, TOTAL_TOKENS_METRIC_KEY: total_tokens, } - span.set_tag_str(METRICS, safe_json(metrics)) + span._set_ctx_item(METRICS, metrics) def _extract_tool_calls(self, chat_completion_msg: Any) -> List[Dict[str, Any]]: """Extracts tool calls from a langchain chat completion.""" @@ -301,20 +306,17 @@ def _handle_stream_input_messages(self, inputs): return input_messages def _llmobs_set_meta_tags_from_chain(self, span: Span, args, kwargs, outputs: Any) -> None: - span.set_tag_str(SPAN_KIND, "workflow") - stream = span.get_tag("langchain.request.stream") - if stream: + if span.get_tag("langchain.request.stream"): inputs = get_argument_value(args, kwargs, 0, "input") else: inputs = kwargs + formatted_inputs = "" if inputs is not None: formatted_inputs = self.format_io(inputs) - span.set_tag_str(INPUT_VALUE, safe_json(formatted_inputs)) - if span.error or outputs is None: - span.set_tag_str(OUTPUT_VALUE, "") - return - formatted_outputs = self.format_io(outputs) - span.set_tag_str(OUTPUT_VALUE, safe_json(formatted_outputs)) + formatted_outputs = "" + if not span.error and outputs is not None: + formatted_outputs = self.format_io(outputs) + span._set_ctx_items({SPAN_KIND: "workflow", INPUT_VALUE: formatted_inputs, OUTPUT_VALUE: formatted_outputs}) def _llmobs_set_meta_tags_from_embedding( self, @@ -324,13 +326,15 @@ def _llmobs_set_meta_tags_from_embedding( output_embedding: Union[List[float], List[List[float]], None], is_workflow: bool = False, ) -> None: - span.set_tag_str(SPAN_KIND, "workflow" if is_workflow else "embedding") - span.set_tag_str(MODEL_NAME, span.get_tag(MODEL) or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag(PROVIDER) or "") - + span._set_ctx_items( + { + SPAN_KIND: "workflow" if is_workflow else "embedding", + MODEL_NAME: span.get_tag(MODEL) or "", + MODEL_PROVIDER: span.get_tag(PROVIDER) or "", + } + ) input_tag_key = INPUT_VALUE if is_workflow else INPUT_DOCUMENTS output_tag_key = OUTPUT_VALUE - output_values: Any try: @@ -343,16 +347,16 @@ def _llmobs_set_meta_tags_from_embedding( ): if is_workflow: formatted_inputs = self.format_io(input_texts) - span.set_tag_str(input_tag_key, safe_json(formatted_inputs)) + span._set_ctx_item(input_tag_key, formatted_inputs) else: if isinstance(input_texts, str): input_texts = [input_texts] input_documents = [Document(text=str(doc)) for doc in input_texts] - span.set_tag_str(input_tag_key, safe_json(input_documents)) + span._set_ctx_item(input_tag_key, input_documents) except TypeError: log.warning("Failed to serialize embedding input data to JSON") if span.error or output_embedding is None: - span.set_tag_str(output_tag_key, "") + span._set_ctx_item(output_tag_key, "") return try: if isinstance(output_embedding[0], float): @@ -364,7 +368,7 @@ def _llmobs_set_meta_tags_from_embedding( output_values = output_embedding embeddings_count = len(output_embedding) embedding_dim = len(output_values[0]) - span.set_tag_str( + span._set_ctx_item( output_tag_key, "[{} embedding(s) returned with size {}]".format(embeddings_count, embedding_dim), ) @@ -379,19 +383,22 @@ def _llmobs_set_meta_tags_from_similarity_search( output_documents: Union[List[Any], None], is_workflow: bool = False, ) -> None: - span.set_tag_str(SPAN_KIND, "workflow" if is_workflow else "retrieval") - span.set_tag_str(MODEL_NAME, span.get_tag(MODEL) or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag(PROVIDER) or "") - + span._set_ctx_items( + { + SPAN_KIND: "workflow" if is_workflow else "retrieval", + MODEL_NAME: span.get_tag(MODEL) or "", + MODEL_PROVIDER: span.get_tag(PROVIDER) or "", + } + ) input_query = get_argument_value(args, kwargs, 0, "query") if input_query is not None: formatted_inputs = self.format_io(input_query) - span.set_tag_str(INPUT_VALUE, safe_json(formatted_inputs)) + span._set_ctx_item(INPUT_VALUE, formatted_inputs) if span.error or not output_documents or not isinstance(output_documents, list): - span.set_tag_str(OUTPUT_VALUE, "") + span._set_ctx_item(OUTPUT_VALUE, "") return if is_workflow: - span.set_tag_str(OUTPUT_VALUE, "[{} document(s) retrieved]".format(len(output_documents))) + span._set_ctx_item(OUTPUT_VALUE, "[{} document(s) retrieved]".format(len(output_documents))) return documents = [] for d in output_documents: @@ -400,32 +407,31 @@ def _llmobs_set_meta_tags_from_similarity_search( metadata = getattr(d, "metadata", {}) doc["name"] = metadata.get("name", doc["id"]) documents.append(doc) - span.set_tag_str(OUTPUT_DOCUMENTS, safe_json(self.format_io(documents))) + span._set_ctx_item(OUTPUT_DOCUMENTS, self.format_io(documents)) # we set the value as well to ensure that the UI would display it in case the span was the root - span.set_tag_str(OUTPUT_VALUE, "[{} document(s) retrieved]".format(len(documents))) + span._set_ctx_item(OUTPUT_VALUE, "[{} document(s) retrieved]".format(len(documents))) def _llmobs_set_meta_tags_from_tool(self, span: Span, tool_inputs: Dict[str, Any], tool_output: object) -> None: - if span.get_tag(METADATA): - metadata = json.loads(str(span.get_tag(METADATA))) - else: - metadata = {} - - span.set_tag_str(SPAN_KIND, "tool") + metadata = json.loads(str(span.get_tag(METADATA))) if span.get_tag(METADATA) else {} + formatted_input = "" if tool_inputs is not None: tool_input = tool_inputs.get("input") if tool_inputs.get("config"): metadata["tool_config"] = tool_inputs.get("config") if tool_inputs.get("info"): metadata["tool_info"] = tool_inputs.get("info") - if metadata: - span.set_tag_str(METADATA, safe_json(metadata)) formatted_input = self.format_io(tool_input) - span.set_tag_str(INPUT_VALUE, safe_json(formatted_input)) - if span.error or tool_output is None: - span.set_tag_str(OUTPUT_VALUE, "") - return - formatted_outputs = self.format_io(tool_output) - span.set_tag_str(OUTPUT_VALUE, safe_json(formatted_outputs)) + formatted_outputs = "" + if not span.error and tool_output is not None: + formatted_outputs = self.format_io(tool_output) + span._set_ctx_items( + { + SPAN_KIND: "tool", + METADATA: metadata, + INPUT_VALUE: formatted_input, + OUTPUT_VALUE: formatted_outputs, + } + ) def _set_base_span_tags( # type: ignore[override] self, diff --git a/ddtrace/llmobs/_integrations/openai.py b/ddtrace/llmobs/_integrations/openai.py index 5c9e73eaca7..bd727b1a5a2 100644 --- a/ddtrace/llmobs/_integrations/openai.py +++ b/ddtrace/llmobs/_integrations/openai.py @@ -23,7 +23,6 @@ from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration from ddtrace.llmobs._utils import _get_attr -from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.utils import Document from ddtrace.pin import Pin @@ -148,19 +147,18 @@ def _llmobs_set_tags( ) -> None: """Sets meta tags and metrics for span events to be sent to LLMObs.""" span_kind = "embedding" if operation == "embedding" else "llm" - span.set_tag_str(SPAN_KIND, span_kind) model_name = span.get_tag("openai.response.model") or span.get_tag("openai.request.model") - span.set_tag_str(MODEL_NAME, model_name or "") model_provider = "azure_openai" if self._is_azure_openai(span) else "openai" - span.set_tag_str(MODEL_PROVIDER, model_provider) if operation == "completion": self._llmobs_set_meta_tags_from_completion(span, kwargs, response) elif operation == "chat": self._llmobs_set_meta_tags_from_chat(span, kwargs, response) elif operation == "embedding": self._llmobs_set_meta_tags_from_embedding(span, kwargs, response) - metrics = self._set_llmobs_metrics_tags(span, response) - span.set_tag_str(METRICS, safe_json(metrics)) + metrics = self._extract_llmobs_metrics_tags(span, response) + span._set_ctx_items( + {SPAN_KIND: span_kind, MODEL_NAME: model_name or "", MODEL_PROVIDER: model_provider, METRICS: metrics} + ) @staticmethod def _llmobs_set_meta_tags_from_completion(span: Span, kwargs: Dict[str, Any], completions: Any) -> None: @@ -168,20 +166,18 @@ def _llmobs_set_meta_tags_from_completion(span: Span, kwargs: Dict[str, Any], co prompt = kwargs.get("prompt", "") if isinstance(prompt, str): prompt = [prompt] - span.set_tag_str(INPUT_MESSAGES, safe_json([{"content": str(p)} for p in prompt])) - parameters = {k: v for k, v in kwargs.items() if k not in ("model", "prompt")} - span.set_tag_str(METADATA, safe_json(parameters)) - - if span.error or not completions: - span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) - return - if hasattr(completions, "choices"): # non-streaming response - choices = completions.choices - else: # streamed response - choices = completions - messages = [{"content": _get_attr(choice, "text", "")} for choice in choices] - span.set_tag_str(OUTPUT_MESSAGES, safe_json(messages)) + output_messages = [{"content": ""}] + if not span.error and completions: + choices = getattr(completions, "choices", completions) + output_messages = [{"content": _get_attr(choice, "text", "")} for choice in choices] + span._set_ctx_items( + { + INPUT_MESSAGES: [{"content": str(p)} for p in prompt], + METADATA: parameters, + OUTPUT_MESSAGES: output_messages, + } + ) @staticmethod def _llmobs_set_meta_tags_from_chat(span: Span, kwargs: Dict[str, Any], messages: Optional[Any]) -> None: @@ -189,16 +185,14 @@ def _llmobs_set_meta_tags_from_chat(span: Span, kwargs: Dict[str, Any], messages input_messages = [] for m in kwargs.get("messages", []): input_messages.append({"content": str(_get_attr(m, "content", "")), "role": str(_get_attr(m, "role", ""))}) - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) - parameters = {k: v for k, v in kwargs.items() if k not in ("model", "messages", "tools", "functions")} - span.set_tag_str(METADATA, safe_json(parameters)) + span._set_ctx_items({INPUT_MESSAGES: input_messages, METADATA: parameters}) if span.error or not messages: - span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) + span._set_ctx_item(OUTPUT_MESSAGES, [{"content": ""}]) return - output_messages = [] if isinstance(messages, list): # streamed response + output_messages = [] for streamed_message in messages: message = {"content": streamed_message["content"], "role": streamed_message["role"]} tool_calls = streamed_message.get("tool_calls", []) @@ -213,9 +207,10 @@ def _llmobs_set_meta_tags_from_chat(span: Span, kwargs: Dict[str, Any], messages for tool_call in tool_calls ] output_messages.append(message) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) + span._set_ctx_item(OUTPUT_MESSAGES, output_messages) return choices = _get_attr(messages, "choices", []) + output_messages = [] for idx, choice in enumerate(choices): tool_calls_info = [] choice_message = _get_attr(choice, "message", {}) @@ -241,7 +236,7 @@ def _llmobs_set_meta_tags_from_chat(span: Span, kwargs: Dict[str, Any], messages output_messages.append({"content": content, "role": role, "tool_calls": tool_calls_info}) continue output_messages.append({"content": content, "role": role}) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) + span._set_ctx_item(OUTPUT_MESSAGES, output_messages) @staticmethod def _llmobs_set_meta_tags_from_embedding(span: Span, kwargs: Dict[str, Any], resp: Any) -> None: @@ -250,7 +245,6 @@ def _llmobs_set_meta_tags_from_embedding(span: Span, kwargs: Dict[str, Any], res metadata = {"encoding_format": encoding_format} if kwargs.get("dimensions"): metadata["dimensions"] = kwargs.get("dimensions") - span.set_tag_str(METADATA, safe_json(metadata)) embedding_inputs = kwargs.get("input", "") if isinstance(embedding_inputs, str) or isinstance(embedding_inputs[0], int): @@ -258,20 +252,19 @@ def _llmobs_set_meta_tags_from_embedding(span: Span, kwargs: Dict[str, Any], res input_documents = [] for doc in embedding_inputs: input_documents.append(Document(text=str(doc))) - span.set_tag_str(INPUT_DOCUMENTS, safe_json(input_documents)) - + span._set_ctx_items({METADATA: metadata, INPUT_DOCUMENTS: input_documents}) if span.error: return if encoding_format == "float": embedding_dim = len(resp.data[0].embedding) - span.set_tag_str( + span._set_ctx_item( OUTPUT_VALUE, "[{} embedding(s) returned with size {}]".format(len(resp.data), embedding_dim) ) return - span.set_tag_str(OUTPUT_VALUE, "[{} embedding(s) returned]".format(len(resp.data))) + span._set_ctx_item(OUTPUT_VALUE, "[{} embedding(s) returned]".format(len(resp.data))) @staticmethod - def _set_llmobs_metrics_tags(span: Span, resp: Any) -> Dict[str, Any]: + def _extract_llmobs_metrics_tags(span: Span, resp: Any) -> Dict[str, Any]: """Extract metrics from a chat/completion and set them as a temporary "_ml_obs.metrics" tag.""" token_usage = _get_attr(resp, "usage", None) if token_usage is not None: diff --git a/ddtrace/llmobs/_integrations/vertexai.py b/ddtrace/llmobs/_integrations/vertexai.py index 69fdc7eb665..4019268e0c4 100644 --- a/ddtrace/llmobs/_integrations/vertexai.py +++ b/ddtrace/llmobs/_integrations/vertexai.py @@ -19,7 +19,6 @@ from ddtrace.llmobs._integrations.utils import get_system_instructions_from_google_model from ddtrace.llmobs._integrations.utils import llmobs_get_metadata_google from ddtrace.llmobs._utils import _get_attr -from ddtrace.llmobs._utils import safe_json class VertexAIIntegration(BaseLLMIntegration): @@ -41,30 +40,29 @@ def _llmobs_set_tags( response: Optional[Any] = None, operation: str = "", ) -> None: - span.set_tag_str(SPAN_KIND, "llm") - span.set_tag_str(MODEL_NAME, span.get_tag("vertexai.request.model") or "") - span.set_tag_str(MODEL_PROVIDER, span.get_tag("vertexai.request.provider") or "") - instance = kwargs.get("instance", None) history = kwargs.get("history", []) metadata = llmobs_get_metadata_google(kwargs, instance) - span.set_tag_str(METADATA, safe_json(metadata)) system_instruction = get_system_instructions_from_google_model(instance) input_contents = get_argument_value(args, kwargs, 0, "contents") input_messages = self._extract_input_message(input_contents, history, system_instruction) - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages)) - - if span.error or response is None: - span.set_tag_str(OUTPUT_MESSAGES, safe_json([{"content": ""}])) - return - output_messages = self._extract_output_message(response) - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages)) + output_messages = [{"content": ""}] + if not span.error and response is not None: + output_messages = self._extract_output_message(response) - usage = get_llmobs_metrics_tags_google("vertexai", span) - if usage: - span.set_tag_str(METRICS, safe_json(usage)) + span._set_ctx_items( + { + SPAN_KIND: "llm", + MODEL_NAME: span.get_tag("vertexai.request.model") or "", + MODEL_PROVIDER: span.get_tag("vertexai.request.provider") or "", + METADATA: metadata, + INPUT_MESSAGES: input_messages, + OUTPUT_MESSAGES: output_messages, + METRICS: get_llmobs_metrics_tags_google("vertexai", span), + } + ) def _extract_input_message(self, contents, history, system_instruction=None): from vertexai.generative_models._generative_models import Part diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 808cee89e0f..867edbdca4f 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -399,23 +399,23 @@ def _start_span( if name is None: name = operation_kind span = self.tracer.trace(name, resource=operation_kind, span_type=SpanTypes.LLM) - span.set_tag_str(SPAN_KIND, operation_kind) + span._set_ctx_item(SPAN_KIND, operation_kind) if model_name is not None: - span.set_tag_str(MODEL_NAME, model_name) + span._set_ctx_item(MODEL_NAME, model_name) if model_provider is not None: - span.set_tag_str(MODEL_PROVIDER, model_provider) + span._set_ctx_item(MODEL_PROVIDER, model_provider) session_id = session_id if session_id is not None else _get_session_id(span) if session_id is not None: - span.set_tag_str(SESSION_ID, session_id) + span._set_ctx_item(SESSION_ID, session_id) if ml_app is None: ml_app = _get_ml_app(span) - span.set_tag_str(ML_APP, ml_app) - if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: + span._set_ctx_item(ML_APP, ml_app) + if span._get_ctx_item(PROPAGATED_PARENT_ID_KEY) is None: # For non-distributed traces or spans in the first service of a distributed trace, # The LLMObs parent ID tag is not set at span start time. We need to manually set the parent ID tag now # in these cases to avoid conflicting with the later propagated tags. parent_id = _get_llmobs_parent_id(span) or "undefined" - span.set_tag_str(PARENT_ID_KEY, str(parent_id)) + span._set_ctx_item(PARENT_ID_KEY, str(parent_id)) return span @classmethod @@ -638,7 +638,7 @@ def annotate( cls._tag_metrics(span, metrics) if tags is not None: cls._tag_span_tags(span, tags) - span_kind = span.get_tag(SPAN_KIND) + span_kind = span._get_ctx_item(SPAN_KIND) if parameters is not None: log.warning("Setting parameters is deprecated, please set parameters and other metadata as tags instead.") cls._tag_params(span, parameters) @@ -664,7 +664,7 @@ def _tag_prompt(span, prompt: dict) -> None: """Tags a given LLMObs span with a prompt""" try: validated_prompt = validate_prompt(prompt) - span.set_tag_str(INPUT_PROMPT, safe_json(validated_prompt)) + span._set_ctx_item(INPUT_PROMPT, validated_prompt) except TypeError: log.warning("Failed to validate prompt with error: ", exc_info=True) return @@ -677,7 +677,7 @@ def _tag_params(span: Span, params: Dict[str, Any]) -> None: if not isinstance(params, dict): log.warning("parameters must be a dictionary of key-value pairs.") return - span.set_tag_str(INPUT_PARAMETERS, safe_json(params)) + span._set_ctx_item(INPUT_PARAMETERS, params) @classmethod def _tag_llm_io(cls, span, input_messages=None, output_messages=None): @@ -689,7 +689,7 @@ def _tag_llm_io(cls, span, input_messages=None, output_messages=None): if not isinstance(input_messages, Messages): input_messages = Messages(input_messages) if input_messages.messages: - span.set_tag_str(INPUT_MESSAGES, safe_json(input_messages.messages)) + span._set_ctx_item(INPUT_MESSAGES, input_messages.messages) except TypeError: log.warning("Failed to parse input messages.", exc_info=True) if output_messages is None: @@ -699,7 +699,7 @@ def _tag_llm_io(cls, span, input_messages=None, output_messages=None): output_messages = Messages(output_messages) if not output_messages.messages: return - span.set_tag_str(OUTPUT_MESSAGES, safe_json(output_messages.messages)) + span._set_ctx_item(OUTPUT_MESSAGES, output_messages.messages) except TypeError: log.warning("Failed to parse output messages.", exc_info=True) @@ -713,12 +713,12 @@ def _tag_embedding_io(cls, span, input_documents=None, output_text=None): if not isinstance(input_documents, Documents): input_documents = Documents(input_documents) if input_documents.documents: - span.set_tag_str(INPUT_DOCUMENTS, safe_json(input_documents.documents)) + span._set_ctx_item(INPUT_DOCUMENTS, input_documents.documents) except TypeError: log.warning("Failed to parse input documents.", exc_info=True) if output_text is None: return - span.set_tag_str(OUTPUT_VALUE, safe_json(output_text)) + span._set_ctx_item(OUTPUT_VALUE, str(output_text)) @classmethod def _tag_retrieval_io(cls, span, input_text=None, output_documents=None): @@ -726,7 +726,7 @@ def _tag_retrieval_io(cls, span, input_text=None, output_documents=None): Will be mapped to span's `meta.{input,output}.text` fields. """ if input_text is not None: - span.set_tag_str(INPUT_VALUE, safe_json(input_text)) + span._set_ctx_item(INPUT_VALUE, str(input_text)) if output_documents is None: return try: @@ -734,7 +734,7 @@ def _tag_retrieval_io(cls, span, input_text=None, output_documents=None): output_documents = Documents(output_documents) if not output_documents.documents: return - span.set_tag_str(OUTPUT_DOCUMENTS, safe_json(output_documents.documents)) + span._set_ctx_item(OUTPUT_DOCUMENTS, output_documents.documents) except TypeError: log.warning("Failed to parse output documents.", exc_info=True) @@ -744,9 +744,9 @@ def _tag_text_io(cls, span, input_value=None, output_value=None): Will be mapped to span's `meta.{input,output}.values` fields. """ if input_value is not None: - span.set_tag_str(INPUT_VALUE, safe_json(input_value)) + span._set_ctx_item(INPUT_VALUE, str(input_value)) if output_value is not None: - span.set_tag_str(OUTPUT_VALUE, safe_json(output_value)) + span._set_ctx_item(OUTPUT_VALUE, str(output_value)) @staticmethod def _tag_span_tags(span: Span, span_tags: Dict[str, Any]) -> None: @@ -759,12 +759,9 @@ def _tag_span_tags(span: Span, span_tags: Dict[str, Any]) -> None: log.warning("span_tags must be a dictionary of string key - primitive value pairs.") return try: - current_tags_str = span.get_tag(TAGS) - if current_tags_str: - current_tags = json.loads(current_tags_str) - current_tags.update(span_tags) - span_tags = current_tags - span.set_tag_str(TAGS, safe_json(span_tags)) + existing_tags = span._get_ctx_item(TAGS) or {} + existing_tags.update(span_tags) + span._set_ctx_item(TAGS, existing_tags) except Exception: log.warning("Failed to parse tags.", exc_info=True) @@ -776,7 +773,7 @@ def _tag_metadata(span: Span, metadata: Dict[str, Any]) -> None: if not isinstance(metadata, dict): log.warning("metadata must be a dictionary of string key-value pairs.") return - span.set_tag_str(METADATA, safe_json(metadata)) + span._set_ctx_item(METADATA, metadata) @staticmethod def _tag_metrics(span: Span, metrics: Dict[str, Any]) -> None: @@ -786,7 +783,7 @@ def _tag_metrics(span: Span, metrics: Dict[str, Any]) -> None: if not isinstance(metrics, dict): log.warning("metrics must be a dictionary of string key - numeric value pairs.") return - span.set_tag_str(METRICS, safe_json(metrics)) + span._set_ctx_item(METRICS, metrics) @classmethod def submit_evaluation( diff --git a/ddtrace/llmobs/_trace_processor.py b/ddtrace/llmobs/_trace_processor.py index b4af0c5ffd1..231d53d7626 100644 --- a/ddtrace/llmobs/_trace_processor.py +++ b/ddtrace/llmobs/_trace_processor.py @@ -1,4 +1,3 @@ -import json from typing import Any from typing import Dict from typing import List @@ -27,7 +26,6 @@ from ddtrace.llmobs._constants import OUTPUT_DOCUMENTS from ddtrace.llmobs._constants import OUTPUT_MESSAGES from ddtrace.llmobs._constants import OUTPUT_VALUE -from ddtrace.llmobs._constants import PARENT_ID_KEY from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX from ddtrace.llmobs._constants import RUNNER_IS_INTEGRATION_SPAN_TAG from ddtrace.llmobs._constants import SESSION_ID @@ -37,6 +35,7 @@ from ddtrace.llmobs._utils import _get_ml_app from ddtrace.llmobs._utils import _get_session_id from ddtrace.llmobs._utils import _get_span_name +from ddtrace.llmobs._utils import safe_json log = get_logger(__name__) @@ -62,7 +61,7 @@ def process_trace(self, trace: List[Span]) -> Optional[List[Span]]: def submit_llmobs_span(self, span: Span) -> None: """Generate and submit an LLMObs span event to be sent to LLMObs.""" span_event = None - is_llm_span = span.get_tag(SPAN_KIND) == "llm" + is_llm_span = span._get_ctx_item(SPAN_KIND) == "llm" is_ragas_integration_span = False try: span_event, is_ragas_integration_span = self._llmobs_span_event(span) @@ -77,44 +76,49 @@ def submit_llmobs_span(self, span: Span) -> None: def _llmobs_span_event(self, span: Span) -> Tuple[Dict[str, Any], bool]: """Span event object structure.""" - span_kind = span._meta.pop(SPAN_KIND) + span_kind = span._get_ctx_item(SPAN_KIND) + if not span_kind: + raise KeyError("Span kind not found in span context") meta: Dict[str, Any] = {"span.kind": span_kind, "input": {}, "output": {}} - if span_kind in ("llm", "embedding") and span.get_tag(MODEL_NAME) is not None: - meta["model_name"] = span._meta.pop(MODEL_NAME) - meta["model_provider"] = span._meta.pop(MODEL_PROVIDER, "custom").lower() - if span.get_tag(METADATA) is not None: - meta["metadata"] = json.loads(span._meta.pop(METADATA)) - if span.get_tag(INPUT_PARAMETERS): - meta["input"]["parameters"] = json.loads(span._meta.pop(INPUT_PARAMETERS)) - if span_kind == "llm" and span.get_tag(INPUT_MESSAGES) is not None: - meta["input"]["messages"] = json.loads(span._meta.pop(INPUT_MESSAGES)) - if span.get_tag(INPUT_VALUE) is not None: - meta["input"]["value"] = span._meta.pop(INPUT_VALUE) - if span_kind == "llm" and span.get_tag(OUTPUT_MESSAGES) is not None: - meta["output"]["messages"] = json.loads(span._meta.pop(OUTPUT_MESSAGES)) - if span_kind == "embedding" and span.get_tag(INPUT_DOCUMENTS) is not None: - meta["input"]["documents"] = json.loads(span._meta.pop(INPUT_DOCUMENTS)) - if span.get_tag(OUTPUT_VALUE) is not None: - meta["output"]["value"] = span._meta.pop(OUTPUT_VALUE) - if span_kind == "retrieval" and span.get_tag(OUTPUT_DOCUMENTS) is not None: - meta["output"]["documents"] = json.loads(span._meta.pop(OUTPUT_DOCUMENTS)) - if span.get_tag(INPUT_PROMPT) is not None: - prompt_json_str = span._meta.pop(INPUT_PROMPT) + if span_kind in ("llm", "embedding") and span._get_ctx_item(MODEL_NAME) is not None: + meta["model_name"] = span._get_ctx_item(MODEL_NAME) + meta["model_provider"] = (span._get_ctx_item(MODEL_PROVIDER) or "custom").lower() + meta["metadata"] = span._get_ctx_item(METADATA) or {} + if span._get_ctx_item(INPUT_PARAMETERS): + meta["input"]["parameters"] = span._get_ctx_item(INPUT_PARAMETERS) + if span_kind == "llm" and span._get_ctx_item(INPUT_MESSAGES) is not None: + meta["input"]["messages"] = span._get_ctx_item(INPUT_MESSAGES) + if span._get_ctx_item(INPUT_VALUE) is not None: + meta["input"]["value"] = safe_json(span._get_ctx_item(INPUT_VALUE)) + if span_kind == "llm" and span._get_ctx_item(OUTPUT_MESSAGES) is not None: + meta["output"]["messages"] = span._get_ctx_item(OUTPUT_MESSAGES) + if span_kind == "embedding" and span._get_ctx_item(INPUT_DOCUMENTS) is not None: + meta["input"]["documents"] = span._get_ctx_item(INPUT_DOCUMENTS) + if span._get_ctx_item(OUTPUT_VALUE) is not None: + meta["output"]["value"] = safe_json(span._get_ctx_item(OUTPUT_VALUE)) + if span_kind == "retrieval" and span._get_ctx_item(OUTPUT_DOCUMENTS) is not None: + meta["output"]["documents"] = span._get_ctx_item(OUTPUT_DOCUMENTS) + if span._get_ctx_item(INPUT_PROMPT) is not None: + prompt_json_str = span._get_ctx_item(INPUT_PROMPT) if span_kind != "llm": log.warning( "Dropping prompt on non-LLM span kind, annotating prompts is only supported for LLM span kinds." ) else: - meta["input"]["prompt"] = json.loads(prompt_json_str) + meta["input"]["prompt"] = prompt_json_str if span.error: - meta[ERROR_MSG] = span.get_tag(ERROR_MSG) - meta[ERROR_STACK] = span.get_tag(ERROR_STACK) - meta[ERROR_TYPE] = span.get_tag(ERROR_TYPE) + meta.update( + { + ERROR_MSG: span.get_tag(ERROR_MSG), + ERROR_STACK: span.get_tag(ERROR_STACK), + ERROR_TYPE: span.get_tag(ERROR_TYPE), + } + ) if not meta["input"]: meta.pop("input") if not meta["output"]: meta.pop("output") - metrics = json.loads(span._meta.pop(METRICS, "{}")) + metrics = span._get_ctx_item(METRICS) or {} ml_app = _get_ml_app(span) is_ragas_integration_span = False @@ -122,10 +126,8 @@ def _llmobs_span_event(self, span: Span) -> Tuple[Dict[str, Any], bool]: if ml_app.startswith(RAGAS_ML_APP_PREFIX): is_ragas_integration_span = True - span.set_tag_str(ML_APP, ml_app) - + span._set_ctx_item(ML_APP, ml_app) parent_id = str(_get_llmobs_parent_id(span) or "undefined") - span._meta.pop(PARENT_ID_KEY, None) llmobs_span_event = { "trace_id": "{:x}".format(span.trace_id), @@ -140,7 +142,7 @@ def _llmobs_span_event(self, span: Span) -> Tuple[Dict[str, Any], bool]: } session_id = _get_session_id(span) if session_id is not None: - span.set_tag_str(SESSION_ID, session_id) + span._set_ctx_item(SESSION_ID, session_id) llmobs_span_event["session_id"] = session_id llmobs_span_event["tags"] = self._llmobs_tags( @@ -169,7 +171,7 @@ def _llmobs_tags( tags["session_id"] = session_id if is_ragas_integration_span: tags[RUNNER_IS_INTEGRATION_SPAN_TAG] = "ragas" - existing_tags = span._meta.pop(TAGS, None) + existing_tags = span._get_ctx_item(TAGS) if existing_tags is not None: - tags.update(json.loads(existing_tags)) + tags.update(existing_tags) return ["{}:{}".format(k, v) for k, v in tags.items()] diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 8813788f0a3..c1b1c4a776c 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -110,8 +110,8 @@ def _get_llmobs_parent_id(span: Span) -> Optional[str]: """Return the span ID of the nearest LLMObs-type span in the span's ancestor tree. In priority order: manually set parent ID tag, nearest LLMObs ancestor, local root's propagated parent ID tag. """ - if span.get_tag(PARENT_ID_KEY): - return span.get_tag(PARENT_ID_KEY) + if span._get_ctx_item(PARENT_ID_KEY): + return span._get_ctx_item(PARENT_ID_KEY) nearest_llmobs_ancestor = _get_nearest_llmobs_ancestor(span) if nearest_llmobs_ancestor: return str(nearest_llmobs_ancestor.span_id) @@ -132,12 +132,12 @@ def _get_ml_app(span: Span) -> str: Return the ML app name for a given span, by checking the span's nearest LLMObs span ancestor. Default to the global config LLMObs ML app name otherwise. """ - ml_app = span.get_tag(ML_APP) + ml_app = span._get_ctx_item(ML_APP) if ml_app: return ml_app nearest_llmobs_ancestor = _get_nearest_llmobs_ancestor(span) if nearest_llmobs_ancestor: - ml_app = nearest_llmobs_ancestor.get_tag(ML_APP) + ml_app = nearest_llmobs_ancestor._get_ctx_item(ML_APP) return ml_app or config._llmobs_ml_app or "unknown-ml-app" @@ -146,12 +146,12 @@ def _get_session_id(span: Span) -> Optional[str]: Return the session ID for a given span, by checking the span's nearest LLMObs span ancestor. Default to the span's trace ID. """ - session_id = span.get_tag(SESSION_ID) + session_id = span._get_ctx_item(SESSION_ID) if session_id: return session_id nearest_llmobs_ancestor = _get_nearest_llmobs_ancestor(span) if nearest_llmobs_ancestor: - session_id = nearest_llmobs_ancestor.get_tag(SESSION_ID) + session_id = nearest_llmobs_ancestor._get_ctx_item(SESSION_ID) return session_id diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 6496de96cfe..5a293f05c4e 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -1,5 +1,4 @@ import atexit -import json from typing import Any from typing import Dict from typing import List @@ -32,6 +31,7 @@ from ddtrace.llmobs._constants import EVP_PROXY_AGENT_ENDPOINT from ddtrace.llmobs._constants import EVP_SUBDOMAIN_HEADER_NAME from ddtrace.llmobs._constants import EVP_SUBDOMAIN_HEADER_VALUE +from ddtrace.llmobs._utils import safe_json logger = get_logger(__name__) @@ -108,11 +108,7 @@ def periodic(self) -> None: self._buffer = [] data = self._data(events) - try: - enc_llm_events = json.dumps(data) - except TypeError: - logger.error("failed to encode %d LLMObs %s events", len(events), self._event_type, exc_info=True) - return + enc_llm_events = safe_json(data) conn = httplib.HTTPSConnection(self._intake, 443, timeout=self._timeout) try: conn.request("POST", self._endpoint, enc_llm_events, self._headers) @@ -197,7 +193,7 @@ def put(self, events: List[LLMObsSpanEvent]): ) return self._buffer.extend(events) - self.buffer_size += len(json.dumps(events)) + self.buffer_size += len(safe_json(events)) def encode(self): with self._lock: @@ -207,7 +203,7 @@ def encode(self): self._init_buffer() data = {"_dd.stage": "raw", "_dd.tracer_version": ddtrace.__version__, "event_type": "span", "spans": events} try: - enc_llm_events = json.dumps(data) + enc_llm_events = safe_json(data) logger.debug("encode %d LLMObs span events to be sent", len(events)) except TypeError: logger.error("failed to encode %d LLMObs span events", len(events), exc_info=True) @@ -277,7 +273,7 @@ def stop(self, timeout=None): super(LLMObsSpanWriter, self).stop(timeout=timeout) def enqueue(self, event: LLMObsSpanEvent) -> None: - event_size = len(json.dumps(event)) + event_size = len(safe_json(event)) if event_size >= EVP_EVENT_SIZE_LIMIT: logger.warning( diff --git a/ddtrace/llmobs/decorators.py b/ddtrace/llmobs/decorators.py index 93f329f2889..7e61f9b4e18 100644 --- a/ddtrace/llmobs/decorators.py +++ b/ddtrace/llmobs/decorators.py @@ -172,7 +172,7 @@ def generator_wrapper(*args, **kwargs): func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) if _automatic_io_annotation and bound_args.arguments: - LLMObs.annotate(span=span, input_data=bound_args.arguments) + LLMObs.annotate(span=span, input_data=dict(bound_args.arguments)) return yield_from_async_gen(func, span, args, kwargs) @wraps(func) @@ -186,13 +186,13 @@ async def wrapper(*args, **kwargs): func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) if _automatic_io_annotation and bound_args.arguments: - LLMObs.annotate(span=span, input_data=bound_args.arguments) + LLMObs.annotate(span=span, input_data=dict(bound_args.arguments)) resp = await func(*args, **kwargs) if ( _automatic_io_annotation and resp and operation_kind != "retrieval" - and span.get_tag(OUTPUT_VALUE) is None + and span._get_ctx_item(OUTPUT_VALUE) is None ): LLMObs.annotate(span=span, output_data=resp) return resp @@ -211,7 +211,7 @@ def generator_wrapper(*args, **kwargs): func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) if _automatic_io_annotation and bound_args.arguments: - LLMObs.annotate(span=span, input_data=bound_args.arguments) + LLMObs.annotate(span=span, input_data=dict(bound_args.arguments)) try: yield from func(*args, **kwargs) except (StopIteration, GeneratorExit): @@ -234,13 +234,13 @@ def wrapper(*args, **kwargs): func_signature = signature(func) bound_args = func_signature.bind_partial(*args, **kwargs) if _automatic_io_annotation and bound_args.arguments: - LLMObs.annotate(span=span, input_data=bound_args.arguments) + LLMObs.annotate(span=span, input_data=dict(bound_args.arguments)) resp = func(*args, **kwargs) if ( _automatic_io_annotation and resp and operation_kind != "retrieval" - and span.get_tag(OUTPUT_VALUE) is None + and span._get_ctx_item(OUTPUT_VALUE) is None ): LLMObs.annotate(span=span, output_data=resp) return resp diff --git a/tests/contrib/anthropic/test_anthropic_llmobs.py b/tests/contrib/anthropic/test_anthropic_llmobs.py index f286a890209..e2850a4157f 100644 --- a/tests/contrib/anthropic/test_anthropic_llmobs.py +++ b/tests/contrib/anthropic/test_anthropic_llmobs.py @@ -1,6 +1,5 @@ from pathlib import Path -import mock import pytest from tests.llmobs._utils import _expected_llmobs_llm_span_event @@ -117,37 +116,6 @@ def test_error(self, anthropic, ddtrace_global_config, mock_llmobs_writer, mock_ ) ) - def test_error_unserializable_arg( - self, anthropic, ddtrace_global_config, mock_llmobs_writer, mock_tracer, request_vcr - ): - """Ensure we handle unserializable arguments correctly and still emit llmobs records.""" - llm = anthropic.Anthropic() - with pytest.raises(Exception): - llm.messages.create( - model="claude-3-opus-20240229", - max_tokens=object(), - temperature=0.8, - messages=[{"role": "user", "content": "Hello World!"}], - ) - - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_span = _expected_llmobs_llm_span_event( - span, - model_name="claude-3-opus-20240229", - model_provider="anthropic", - input_messages=[{"content": "Hello World!", "role": "user"}], - output_messages=[{"content": ""}], - error=span.get_tag("error.type"), - error_message=span.get_tag("error.message"), - error_stack=span.get_tag("error.stack"), - metadata={"temperature": 0.8, "max_tokens": mock.ANY}, - tags={"ml_app": "", "service": "tests.contrib.anthropic"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_span) - actual_span = mock_llmobs_writer.enqueue.call_args[0][0] - assert "[Unserializable object: ", "service": "tests.contrib.openai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_span) - actual_span = mock_llmobs_writer.enqueue.call_args[0][0] - assert "[Unserializable object: Date: Fri, 13 Dec 2024 14:28:59 +0100 Subject: [PATCH 300/372] chore(iast): move env var to config (#11720) Moving `DD_IAST_VULNERABILITIES_PER_REQUEST` and `DD_IAST_MAX_CONCURRENT_REQUESTS` to `asm_config` ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_constants.py | 4 ++++ ddtrace/appsec/_iast/__init__.py | 2 +- ddtrace/appsec/_iast/_overhead_control_engine.py | 14 +++++--------- ddtrace/appsec/_iast/taint_sinks/_base.py | 8 +++++--- ddtrace/settings/asm.py | 12 ++++++++++++ tests/appsec/iast/test_overhead_control_engine.py | 12 +++++------- tests/telemetry/test_writer.py | 2 ++ 7 files changed, 34 insertions(+), 20 deletions(-) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index a127ebb6615..fbb1264f6f9 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -129,6 +129,10 @@ class IAST(metaclass=Constant_Class): ENV_DEBUG: Literal["DD_IAST_DEBUG"] = "DD_IAST_DEBUG" ENV_PROPAGATION_DEBUG: Literal["DD_IAST_PROPAGATION_DEBUG"] = "DD_IAST_PROPAGATION_DEBUG" ENV_REQUEST_SAMPLING: Literal["DD_IAST_REQUEST_SAMPLING"] = "DD_IAST_REQUEST_SAMPLING" + DD_IAST_VULNERABILITIES_PER_REQUEST: Literal[ + "DD_IAST_VULNERABILITIES_PER_REQUEST" + ] = "DD_IAST_VULNERABILITIES_PER_REQUEST" + DD_IAST_MAX_CONCURRENT_REQUESTS: Literal["DD_IAST_MAX_CONCURRENT_REQUESTS"] = "DD_IAST_MAX_CONCURRENT_REQUESTS" ENV_TELEMETRY_REPORT_LVL: Literal["DD_IAST_TELEMETRY_VERBOSITY"] = "DD_IAST_TELEMETRY_VERBOSITY" LAZY_TAINT: Literal["_DD_IAST_LAZY_TAINT"] = "_DD_IAST_LAZY_TAINT" JSON: Literal["_dd.iast.json"] = "_dd.iast.json" diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index 5aab86cf783..3e4b04a0b6a 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -1,4 +1,4 @@ -"""IAST (interactive application security testing) analyzes code for security vulnerabilities. +"""IAST (Interactive Application Security Testing) analyzes code for security vulnerabilities. To add new vulnerabilities analyzers (Taint sink) we should update `IAST_PATCH` in `ddtrace/appsec/iast/_patch_modules.py` diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index 036e4d3cbfd..b1f490b14ef 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -3,7 +3,6 @@ limit. It will measure operations being executed in a request and it will deactivate detection (and therefore reduce the overhead to nearly 0) if a certain threshold is reached. """ -import os from typing import Set from typing import Text from typing import Tuple @@ -25,22 +24,18 @@ def get_request_sampling_value() -> float: return float(asm_config._iast_request_sampling) -MAX_REQUESTS = int(os.environ.get("DD_IAST_MAX_CONCURRENT_REQUESTS", 2)) -MAX_VULNERABILITIES_PER_REQUEST = int(os.environ.get("DD_IAST_VULNERABILITIES_PER_REQUEST", 2)) - - class Operation(object): """Common operation related to Overhead Control Engine (OCE). Every vulnerabilities/taint_sinks should inherit from this class. OCE instance calls these methods to control the overhead produced in each request. """ _lock = threading.Lock() - _vulnerability_quota = MAX_VULNERABILITIES_PER_REQUEST + _vulnerability_quota = asm_config._iast_max_vulnerabilities_per_requests _reported_vulnerabilities: Set[Tuple[str, int]] = set() @classmethod def reset(cls): - cls._vulnerability_quota = MAX_VULNERABILITIES_PER_REQUEST + cls._vulnerability_quota = asm_config._iast_max_vulnerabilities_per_requests cls._reported_vulnerabilities = set() @classmethod @@ -57,7 +52,7 @@ def acquire_quota(cls) -> bool: def increment_quota(cls) -> bool: cls._lock.acquire() result = False - if cls._vulnerability_quota < MAX_VULNERABILITIES_PER_REQUEST: + if cls._vulnerability_quota < asm_config._iast_max_vulnerabilities_per_requests: cls._vulnerability_quota += 1 result = True cls._lock.release() @@ -86,12 +81,13 @@ class OverheadControl(object): """ _lock = threading.Lock() - _request_quota = MAX_REQUESTS + _request_quota = asm_config._iast_max_concurrent_requests _vulnerabilities: Set[Type[Operation]] = set() _sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) def reconfigure(self): self._sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) + self._request_quota = asm_config._iast_max_concurrent_requests def acquire_request(self, span: Span) -> bool: """Decide whether if IAST analysis will be done for this request. diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 7db79d33fd8..16eaac2452c 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -61,9 +61,11 @@ def wrapper(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: vulnerability and update the context with the report information. """ if not is_iast_request_enabled(): - log.debug( - "[IAST] VulnerabilityBase.wrapper. No request quota or this vulnerability is outside the context" - ) + if _is_iast_debug_enabled(): + log.debug( + "[IAST] VulnerabilityBase.wrapper. No request quota or this vulnerability " + "is outside the context" + ) return wrapped(*args, **kwargs) elif cls.has_quota(): return func(wrapped, instance, args, kwargs) diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 3ec15ae67ef..cf20ea08f1a 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -156,6 +156,16 @@ class ASMConfig(Env): + r"ey[I-L][\w=-]+\.ey[I-L][\w=-]+(\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY" + r"[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}", ) + _iast_max_concurrent_requests = Env.var( + int, + IAST.DD_IAST_MAX_CONCURRENT_REQUESTS, + default=2, + ) + _iast_max_vulnerabilities_per_requests = Env.var( + int, + IAST.DD_IAST_VULNERABILITIES_PER_REQUEST, + default=2, + ) _iast_lazy_taint = Env.var(bool, IAST.LAZY_TAINT, default=False) _deduplication_enabled = Env.var(bool, "_DD_APPSEC_DEDUPLICATION_ENABLED", default=True) @@ -213,6 +223,8 @@ class ASMConfig(Env): "_iast_redaction_enabled", "_iast_redaction_name_pattern", "_iast_redaction_value_pattern", + "_iast_max_concurrent_requests", + "_iast_max_vulnerabilities_per_requests", "_iast_lazy_taint", "_ep_stack_trace_enabled", "_ep_max_stack_traces", diff --git a/tests/appsec/iast/test_overhead_control_engine.py b/tests/appsec/iast/test_overhead_control_engine.py index 318f1a2104f..1d1d4d11b90 100644 --- a/tests/appsec/iast/test_overhead_control_engine.py +++ b/tests/appsec/iast/test_overhead_control_engine.py @@ -5,8 +5,7 @@ from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._iast_request_context import get_iast_reporter -from ddtrace.appsec._iast._overhead_control_engine import MAX_REQUESTS -from ddtrace.appsec._iast._overhead_control_engine import MAX_VULNERABILITIES_PER_REQUEST +from ddtrace.settings.asm import config as asm_config from tests.utils import override_global_config @@ -55,7 +54,7 @@ def test_oce_max_vulnerabilities_per_request(iast_context_defaults): m.digest() span_report = get_iast_reporter() - assert len(span_report.vulnerabilities) == MAX_VULNERABILITIES_PER_REQUEST + assert len(span_report.vulnerabilities) == asm_config._iast_max_vulnerabilities_per_requests @pytest.mark.skip_iast_check_logs @@ -72,7 +71,7 @@ def test_oce_reset_vulnerabilities_report(iast_context_defaults): span_report = get_iast_reporter() - assert len(span_report.vulnerabilities) == MAX_VULNERABILITIES_PER_REQUEST + 1 + assert len(span_report.vulnerabilities) == asm_config._iast_max_vulnerabilities_per_requests + 1 @pytest.mark.skip_iast_check_logs @@ -82,7 +81,7 @@ def test_oce_no_race_conditions_in_span(iast_span_defaults): oc = OverheadControl() oc.reconfigure() - assert oc._request_quota == MAX_REQUESTS + assert oc._request_quota == asm_config._iast_max_concurrent_requests # Request 1 tries to acquire the lock assert oc.acquire_request(iast_span_defaults) is True @@ -148,7 +147,6 @@ def test_oce_concurrent_requests_in_spans(iast_span_defaults): """ import threading - from ddtrace.appsec._iast._overhead_control_engine import MAX_REQUESTS from ddtrace.appsec._iast._overhead_control_engine import OverheadControl oc = OverheadControl() @@ -167,7 +165,7 @@ def test_oce_concurrent_requests_in_spans(iast_span_defaults): results.append(thread.join()) # Ensures quota is always within bounds after multithreading scenario - assert 0 <= oc._request_quota <= MAX_REQUESTS + assert 0 <= oc._request_quota <= asm_config._iast_max_concurrent_requests @pytest.mark.skip_iast_check_logs diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 7718710ff60..bcc3be9e38c 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -356,6 +356,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED", "origin": "default", "value": False}, {"name": "DD_HTTP_CLIENT_TAG_QUERY_STRING", "origin": "default", "value": None}, {"name": "DD_IAST_ENABLED", "origin": "default", "value": False}, + {"name": "DD_IAST_MAX_CONCURRENT_REQUESTS", "origin": "default", "value": 2}, {"name": "DD_IAST_REDACTION_ENABLED", "origin": "default", "value": True}, { "name": "DD_IAST_REDACTION_NAME_PATTERN", @@ -380,6 +381,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_IAST_REQUEST_SAMPLING", "origin": "default", "value": 30.0}, {"name": "DD_IAST_STACK_TRACE_ENABLED", "origin": "default", "value": True}, {"name": "DD_IAST_TELEMETRY_VERBOSITY", "origin": "default", "value": "INFORMATION"}, + {"name": "DD_IAST_VULNERABILITIES_PER_REQUEST", "origin": "default", "value": 2}, {"name": "DD_INJECT_FORCE", "origin": "env_var", "value": True}, {"name": "DD_INSTRUMENTATION_INSTALL_ID", "origin": "default", "value": None}, {"name": "DD_INSTRUMENTATION_INSTALL_TYPE", "origin": "default", "value": None}, From d1bce2eb9422a1f0c62f92a67d6ac778c2c1e815 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 13 Dec 2024 11:57:43 -0500 Subject: [PATCH 301/372] ci: fix needs_testrun tests. do not record all requests (#11711) --- scripts/needs_testrun.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index bbbd73b33db..c28ded72812 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -36,7 +36,7 @@ def get_base_branch(pr_number: int) -> str: >>> with vcr.use_cassette( ... "scripts/vcr/needs_testrun.yaml", ... filter_headers=["authorization", "user-agent"], - ... record_mode="all"): + ... record_mode="none"): ... get_base_branch(6412) ... get_base_branch(11534) ... get_base_branch(11690) @@ -135,7 +135,7 @@ def get_changed_files(pr_number: int, sha: t.Optional[str] = None) -> t.Set[str] >>> with vcr.use_cassette( ... "scripts/vcr/needs_testrun.yaml", ... filter_headers=["authorization", "user-agent"], - ... record_mode="all"): + ... record_mode="none"): ... sorted(get_changed_files(6388)) # doctest: +NORMALIZE_WHITESPACE ['ddtrace/debugging/_expressions.py', 'releasenotes/notes/fix-debugger-expressions-none-literal-30f3328d2e386f40.yaml', @@ -165,7 +165,7 @@ def needs_testrun(suite: str, pr_number: int, sha: t.Optional[str] = None) -> bo >>> with vcr.use_cassette( ... "scripts/vcr/needs_testrun.yaml", ... filter_headers=["authorization", "user-agent"], - ... record_mode="all"): + ... record_mode="none"): ... needs_testrun("debugger", 6485) ... needs_testrun("debugger", 6388) ... needs_testrun("foobar", 6412) From 509d39067dfbcd9c1832051b3b60fa00db29dc8e Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 13 Dec 2024 11:58:20 -0500 Subject: [PATCH 302/372] ci: use hatch to execute needs_testrun.py (#11709) --- hatch.toml | 3 +++ scripts/needs_testrun.py | 10 +++++++++- scripts/run-test-suite | 2 +- scripts/run-test-suite-hatch | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hatch.toml b/hatch.toml index f22870e01f1..21610b6c776 100644 --- a/hatch.toml +++ b/hatch.toml @@ -143,6 +143,9 @@ extra-dependencies = [ test = [ "python -m doctest {args} scripts/get-target-milestone.py scripts/needs_testrun.py tests/suitespec.py", ] +needs_testrun = [ + "scripts/needs_testrun.py {args}", +] [envs.meta-testing] python = "3.10" diff --git a/scripts/needs_testrun.py b/scripts/needs_testrun.py index c28ded72812..01ba87299bd 100755 --- a/scripts/needs_testrun.py +++ b/scripts/needs_testrun.py @@ -270,8 +270,13 @@ def extract_git_commit_selections(git_commit_message: str) -> t.Set[str]: def main() -> bool: argp = ArgumentParser() + try: + default_pr_number = _get_pr_number() + except RuntimeError: + default_pr_number = None + argp.add_argument("suite", help="The suite to use", type=str) - argp.add_argument("--pr", help="The PR number", type=int, default=_get_pr_number()) + argp.add_argument("--pr", help="The PR number", type=int, default=default_pr_number) argp.add_argument( "--sha", help="Commit hash to use as diff base (defaults to PR merge root)", type=lambda v: v or None ) @@ -282,6 +287,9 @@ def main() -> bool: if args.verbose: LOGGER.setLevel(logging.INFO) + if not args.pr: + raise RuntimeError("Could not determine PR number") + return needs_testrun(args.suite, args.pr, sha=args.sha) diff --git a/scripts/run-test-suite b/scripts/run-test-suite index cca6bb5262e..8664decde25 100755 --- a/scripts/run-test-suite +++ b/scripts/run-test-suite @@ -47,7 +47,7 @@ set -e if ! [[ -v CIRCLECI && $CIRCLE_BRANCH =~ main ]]; then if [[ -f "$CHECKPOINT_FILENAME" ]]; then latest_success_commit=$(cat $CHECKPOINT_FILENAME) - if ! ./scripts/needs_testrun.py $CIRCLE_JOB --sha $latest_success_commit; then + if ! hatch run scripts:needs_testrun $CIRCLE_JOB --sha $latest_success_commit; then echo "The $CIRCLE_JOB job succeeded at commit $latest_success_commit." echo "None of the changes on this branch since that commit affect the $CIRCLE_JOB job." echo "Skipping this job." diff --git a/scripts/run-test-suite-hatch b/scripts/run-test-suite-hatch index 0a8d1c1d765..38754de9ac9 100755 --- a/scripts/run-test-suite-hatch +++ b/scripts/run-test-suite-hatch @@ -33,7 +33,7 @@ set -e if ! [[ -v CIRCLECI && $CIRCLE_BRANCH =~ main ]]; then if [[ -f "$CHECKPOINT_FILENAME" ]]; then latest_success_commit=$(cat $CHECKPOINT_FILENAME) - if ! ./scripts/needs_testrun.py $CIRCLE_JOB --sha $latest_success_commit; then + if ! hatch run scripts:needs_testrun $CIRCLE_JOB --sha $latest_success_commit; then echo "The $CIRCLE_JOB job succeeded at commit $latest_success_commit." echo "None of the changes on this branch since that commit affect the $CIRCLE_JOB job." echo "Skipping this job." From 0581d4d33237163b16e94c58e87d162aa45a5135 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 13 Dec 2024 13:17:21 -0500 Subject: [PATCH 303/372] ci: wait for es server to be up before running tests (#11707) --- .../contrib/elasticsearch/test_elasticsearch.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/contrib/elasticsearch/test_elasticsearch.py b/tests/contrib/elasticsearch/test_elasticsearch.py index ecb09100387..92ce195c92f 100644 --- a/tests/contrib/elasticsearch/test_elasticsearch.py +++ b/tests/contrib/elasticsearch/test_elasticsearch.py @@ -1,5 +1,7 @@ import datetime +from http.client import HTTPConnection from importlib import import_module +import time import pytest @@ -38,6 +40,18 @@ raise ImportError("could not import any of {0!r}".format(module_names)) +def wait_for_es(host: str, port: int): + for _ in range(20): + try: + conn = HTTPConnection(f"{host}:{port}") + conn.request("GET", "/") + conn.getresponse() + return + except Exception: + time.sleep(1) + raise Exception(f"Could not connect to ES at {host}:{port}") + + class ElasticsearchPatchTest(TracerTestCase): """ Elasticsearch integration test suite. @@ -67,6 +81,8 @@ def setUp(self): super(ElasticsearchPatchTest, self).setUp() es = self._get_es() + config = self._get_es_config() + wait_for_es(config["host"], config["port"]) tags = { # `component` is a reserved tag. Setting it via `Pin` should have no effect. "component": "foo", From 1dd528c0ef5d04e2b095f61d8a15e8fc15cbb00a Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 13 Dec 2024 14:45:27 -0500 Subject: [PATCH 304/372] fix(lib-injection): fix SSI abort telemetry metrics (#11627) --- lib-injection/sources/sitecustomize.py | 156 ++++++++++++------ ...-injection-telemetry-4fbea5e41ee1ff3e.yaml | 4 + 2 files changed, 106 insertions(+), 54 deletions(-) create mode 100644 releasenotes/notes/fix-lib-injection-telemetry-4fbea5e41ee1ff3e.yaml diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index dbc68f65ebe..7d28a3c4d4a 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -31,6 +31,7 @@ def parse_version(version): return Version((0, 0), "") +TELEMETRY_DATA = [] SCRIPT_DIR = os.path.dirname(__file__) RUNTIMES_ALLOW_LIST = { "cpython": { @@ -68,49 +69,58 @@ def get_oci_ddtrace_version(): def build_installed_pkgs(): installed_packages = {} if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata + try: + from importlib import metadata as importlib_metadata - installed_packages = {pkg.metadata["Name"]: pkg.version for pkg in importlib_metadata.distributions()} + installed_packages = {pkg.metadata["Name"]: pkg.version for pkg in importlib_metadata.distributions()} + except Exception as e: + _log("Failed to build installed packages list: %s" % e, level="debug") else: try: import pkg_resources installed_packages = {pkg.key: pkg.version for pkg in pkg_resources.working_set} - except ImportError: + except Exception: try: import importlib_metadata installed_packages = {pkg.metadata["Name"]: pkg.version for pkg in importlib_metadata.distributions()} - except ImportError: - pass + except Exception as e: + _log("Failed to build installed packages list: %s" % e, level="debug") return {key.lower(): value for key, value in installed_packages.items()} def build_min_pkgs(): min_pkgs = dict() - for location in VERSION_COMPAT_FILE_LOCATIONS: - if os.path.exists(location): - with open(location, "r") as csvfile: - csv_reader = csv.reader(csvfile, delimiter=",") - for idx, row in enumerate(csv_reader): - if idx < 2: - continue - min_pkgs[row[0].lower()] = parse_version(row[1]) - break + try: + for location in VERSION_COMPAT_FILE_LOCATIONS: + if os.path.exists(location): + with open(location, "r") as csvfile: + csv_reader = csv.reader(csvfile, delimiter=",") + for idx, row in enumerate(csv_reader): + if idx < 2: + continue + min_pkgs[row[0].lower()] = parse_version(row[1]) + break + except Exception as e: + _log("Failed to build min-pkgs list: %s" % e, level="debug") return min_pkgs def build_denied_executables(): denied_executables = set() _log("Checking denied-executables list", level="debug") - if os.path.exists(EXECUTABLE_DENY_LOCATION): - with open(EXECUTABLE_DENY_LOCATION, "r") as denyfile: - _log("Found deny-list file", level="debug") - for line in denyfile.readlines(): - cleaned = line.strip("\n") - denied_executables.add(cleaned) - denied_executables.add(os.path.basename(cleaned)) - _log("Built denied-executables list of %s entries" % (len(denied_executables),), level="debug") + try: + if os.path.exists(EXECUTABLE_DENY_LOCATION): + with open(EXECUTABLE_DENY_LOCATION, "r") as denyfile: + _log("Found deny-list file", level="debug") + for line in denyfile.readlines(): + cleaned = line.strip("\n") + denied_executables.add(cleaned) + denied_executables.add(os.path.basename(cleaned)) + _log("Built denied-executables list of %s entries" % (len(denied_executables),), level="debug") + except Exception as e: + _log("Failed to build denied-executables list: %s" % e, level="debug") return denied_executables @@ -228,13 +238,14 @@ def _inject(): global PYTHON_RUNTIME global PKGS_ALLOW_LIST global EXECUTABLES_DENY_LIST + global TELEMETRY_DATA + # Try to get the version of the Python runtime first so we have it for telemetry + PYTHON_VERSION = platform.python_version() + PYTHON_RUNTIME = platform.python_implementation().lower() DDTRACE_VERSION = get_oci_ddtrace_version() INSTALLED_PACKAGES = build_installed_pkgs() - PYTHON_RUNTIME = platform.python_implementation().lower() - PYTHON_VERSION = platform.python_version() PKGS_ALLOW_LIST = build_min_pkgs() EXECUTABLES_DENY_LIST = build_denied_executables() - telemetry_data = [] integration_incomp = False runtime_incomp = False os.environ["_DD_INJECT_WAS_ATTEMPTED"] = "true" @@ -260,12 +271,14 @@ def _inject(): _log("ddtrace_pkgs path is %r" % pkgs_path, level="debug") _log("ddtrace_pkgs contents: %r" % os.listdir(pkgs_path), level="debug") + abort = False incompatible_sysarg = get_first_incompatible_sysarg() if incompatible_sysarg is not None: _log("Found incompatible executable: %s." % incompatible_sysarg, level="debug") if not FORCE_INJECT: _log("Aborting dd-trace-py instrumentation.", level="debug") - telemetry_data.append( + abort = True + TELEMETRY_DATA.append( create_count_metric( "library_entrypoint.abort.integration", ) @@ -287,9 +300,10 @@ def _inject(): integration_incomp = True if not FORCE_INJECT: _log("Aborting dd-trace-py instrumentation.", level="debug") + abort = True for key, value in incompatible_packages.items(): - telemetry_data.append( + TELEMETRY_DATA.append( create_count_metric( "library_entrypoint.abort.integration", [ @@ -313,15 +327,16 @@ def _inject(): runtime_incomp = True if not FORCE_INJECT: _log("Aborting dd-trace-py instrumentation.", level="debug") + abort = True - telemetry_data.append(create_count_metric("library_entrypoint.abort.runtime")) + TELEMETRY_DATA.append(create_count_metric("library_entrypoint.abort.runtime")) else: _log( "DD_INJECT_FORCE set to True, allowing unsupported runtimes and continuing.", level="debug", ) - if telemetry_data: - telemetry_data.append( + if abort: + TELEMETRY_DATA.append( create_count_metric( "library_entrypoint.abort", [ @@ -329,8 +344,6 @@ def _inject(): ], ) ) - telemetry_event = gen_telemetry_payload(telemetry_data, DDTRACE_VERSION) - send_telemetry(telemetry_event) return site_pkgs_path = os.path.join( @@ -339,6 +352,12 @@ def _inject(): _log("site-packages path is %r" % site_pkgs_path, level="debug") if not os.path.exists(site_pkgs_path): _log("ddtrace site-packages not found in %r, aborting" % site_pkgs_path, level="error") + TELEMETRY_DATA.append( + gen_telemetry_payload( + [create_count_metric("library_entrypoint.abort", ["reason:missing_" + site_pkgs_path])], + DDTRACE_VERSION, + ) + ) return # Add the custom site-packages directory to the Python path to load the ddtrace package. @@ -349,6 +368,17 @@ def _inject(): except BaseException as e: _log("failed to load ddtrace module: %s" % e, level="error") + TELEMETRY_DATA.append( + gen_telemetry_payload( + [ + create_count_metric( + "library_entrypoint.error", ["error_type:import_ddtrace_" + type(e).__name__.lower()] + ) + ], + DDTRACE_VERSION, + ) + ) + return else: try: @@ -377,38 +407,56 @@ def _inject(): os.environ["PYTHONPATH"] = python_path _log("successfully configured ddtrace package, python path is %r" % os.environ["PYTHONPATH"]) - event = gen_telemetry_payload( - [ - create_count_metric( - "library_entrypoint.complete", - [ - "injection_forced:" + str(runtime_incomp or integration_incomp).lower(), - ], - ) - ], - DDTRACE_VERSION, + TELEMETRY_DATA.append( + gen_telemetry_payload( + [ + create_count_metric( + "library_entrypoint.complete", + [ + "injection_forced:" + str(runtime_incomp or integration_incomp).lower(), + ], + ) + ], + DDTRACE_VERSION, + ) ) - send_telemetry(event) except Exception as e: - event = gen_telemetry_payload( - [create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])], - DDTRACE_VERSION, + TELEMETRY_DATA.append( + gen_telemetry_payload( + [ + create_count_metric( + "library_entrypoint.error", ["error_type:init_ddtrace_" + type(e).__name__.lower()] + ) + ], + DDTRACE_VERSION, + ) ) - send_telemetry(event) _log("failed to load ddtrace.bootstrap.sitecustomize: %s" % e, level="error") return else: module_origin = spec.origin if spec else None _log("user-installed ddtrace found: %s, aborting site-packages injection" % module_origin, level="warning") + TELEMETRY_DATA.append( + create_count_metric( + "library_entrypoint.abort", + [ + "reason:ddtrace_already_present", + ], + ) + ) try: - _inject() -except Exception as e: try: - event = gen_telemetry_payload( - [create_count_metric("library_entrypoint.error", ["error_type:" + type(e).__name__.lower()])] + _inject() + except Exception as e: + TELEMETRY_DATA.append( + gen_telemetry_payload( + [create_count_metric("library_entrypoint.error", ["error_type:main_" + type(e).__name__.lower()])] + ) ) - send_telemetry(event) - except Exception: - pass # absolutely never allow exceptions to propagate to the app + finally: + if TELEMETRY_DATA: + send_telemetry(TELEMETRY_DATA) +except Exception: + pass # absolutely never allow exceptions to propagate to the app diff --git a/releasenotes/notes/fix-lib-injection-telemetry-4fbea5e41ee1ff3e.yaml b/releasenotes/notes/fix-lib-injection-telemetry-4fbea5e41ee1ff3e.yaml new file mode 100644 index 00000000000..cf80f366f5b --- /dev/null +++ b/releasenotes/notes/fix-lib-injection-telemetry-4fbea5e41ee1ff3e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + lib-injection: Fix missing lib-injection telemetry for common abort scenarios. From 00ec1f7d6473e21690ab1b7cac734e4f2d7ac5e7 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:40:18 -0600 Subject: [PATCH 305/372] feat(profiling): add support for pytorch profiling (#9154) PR does - Patches `torch.profiler.profile` class by adding our own `on_trace_ready` handler - Adds GPU time/flops/memory samples via libdatadog interface in `on_trace_ready` event handler - Ensures that libdd exporter is enabled if pytorch is enabled - Hides functionality behind a FF set to False by default - changelog entry - Is there a minimum python version? - the biggest requirement is that the current pytorch profiler API which we instrument was introduced in torch version 1.8.1 (https://pytorch.org/blog/introducing-pytorch-profiler-the-new-and-improved-performance-tool/), do we just want to document or we could disable the instrumentation if we detect an outdated version with `torch.__version__` - Some documentation on needed user configuration, conflicting features, gotchas ~~Probably should make experimental/beta collectors not part of the ALL template (Is this blocking since we haven't done in the past??)~~ ## Testing Done - Tested by running on ec2 GPU instance - Tested by running `prof-pytorch` service in staging - I'm not entirely sure if we need unit tests for this feature, or where they would live. Would we want the unit test suite to depend on torch? Maybe this is solved for tracing integrations, though ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: sanchda Co-authored-by: Peter Griggs Co-authored-by: Daniel Schwartz-Narbonne Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Co-authored-by: Daniel Schwartz-Narbonne Co-authored-by: Taegyun Kim Co-authored-by: Daniel Schwartz-Narbonne --- .github/workflows/pytorch_gpu_tests.yml | 43 ++++ .../dd_wrapper/include/ddup_interface.hpp | 5 + .../dd_wrapper/include/libdatadog_helpers.hpp | 3 +- .../profiling/dd_wrapper/include/sample.hpp | 7 + .../profiling/dd_wrapper/include/types.hpp | 11 +- .../dd_wrapper/src/ddup_interface.cpp | 30 +++ .../profiling/dd_wrapper/src/profile.cpp | 17 ++ .../profiling/dd_wrapper/src/sample.cpp | 58 +++++ .../internal/datadog/profiling/ddup/_ddup.pyi | 25 ++- .../internal/datadog/profiling/ddup/_ddup.pyx | 37 +++ ddtrace/profiling/collector/pytorch.py | 211 ++++++++++++++++++ ddtrace/profiling/profiler.py | 36 +++ ddtrace/settings/profiling.py | 31 ++- docs/advanced_usage.rst | 82 +++++++ docs/pytorch_metric.png | Bin 0 -> 124186 bytes docs/spelling_wordlist.txt | 120 +++++----- hatch.toml | 28 +++ ...-pytorch-integration-0683123b7bb83f99.yaml | 8 + .../simple_program_pytorch_gpu.py | 42 ++++ tests/profiling_v2/test_pytorch.py | 44 ++++ 20 files changed, 770 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/pytorch_gpu_tests.yml create mode 100644 ddtrace/profiling/collector/pytorch.py create mode 100644 docs/pytorch_metric.png create mode 100644 releasenotes/notes/profiling-add-pytorch-integration-0683123b7bb83f99.yaml create mode 100644 tests/profiling_v2/simple_program_pytorch_gpu.py create mode 100644 tests/profiling_v2/test_pytorch.py diff --git a/.github/workflows/pytorch_gpu_tests.yml b/.github/workflows/pytorch_gpu_tests.yml new file mode 100644 index 00000000000..1db504ae61d --- /dev/null +++ b/.github/workflows/pytorch_gpu_tests.yml @@ -0,0 +1,43 @@ +name: Pytorch Unit Tests (with GPU) + +on: + push: + branches: + - 'main' + - 'mq-working-branch**' + paths: + - 'ddtrace/profiling/collector/pytorch.py' + pull_request: + paths: + - 'ddtrace/profiling/collector/pytorch.py' + workflow_dispatch: + +jobs: + unit-tests: + runs-on: APM-4-CORE-GPU-LINUX + steps: + - uses: actions/checkout@v4 + # Include all history and tags + with: + persist-credentials: false + fetch-depth: 0 + + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: '3.12' + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install latest stable toolchain and rustfmt + run: rustup update stable && rustup default stable && rustup component add rustfmt clippy + + - name: Install hatch + uses: pypa/hatch@install + with: + version: "1.12.0" + + - name: Install PyTorch + run: pip install torch + + - name: Run tests + run: hatch run profiling_pytorch:test diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/ddup_interface.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/ddup_interface.hpp index cd18ead1966..0eec6ad87bc 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/ddup_interface.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/ddup_interface.hpp @@ -44,6 +44,9 @@ extern "C" void ddup_push_release(Datadog::Sample* sample, int64_t release_time, int64_t count); void ddup_push_alloc(Datadog::Sample* sample, int64_t size, int64_t count); void ddup_push_heap(Datadog::Sample* sample, int64_t size); + void ddup_push_gpu_gputime(Datadog::Sample* sample, int64_t time, int64_t count); + void ddup_push_gpu_memory(Datadog::Sample* sample, int64_t mem, int64_t count); + void ddup_push_gpu_flops(Datadog::Sample* sample, int64_t flops, int64_t count); void ddup_push_lock_name(Datadog::Sample* sample, std::string_view lock_name); void ddup_push_threadinfo(Datadog::Sample* sample, int64_t thread_id, @@ -56,11 +59,13 @@ extern "C" void ddup_push_trace_type(Datadog::Sample* sample, std::string_view trace_type); void ddup_push_exceptioninfo(Datadog::Sample* sample, std::string_view exception_type, int64_t count); void ddup_push_class_name(Datadog::Sample* sample, std::string_view class_name); + void ddup_push_gpu_device_name(Datadog::Sample*, std::string_view device_name); void ddup_push_frame(Datadog::Sample* sample, std::string_view _name, std::string_view _filename, uint64_t address, int64_t line); + void ddup_push_absolute_ns(Datadog::Sample* sample, int64_t timestamp_ns); void ddup_push_monotonic_ns(Datadog::Sample* sample, int64_t monotonic_ns); void ddup_flush_sample(Datadog::Sample* sample); // Stack v2 specific flush, which reverses the locations diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/libdatadog_helpers.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/libdatadog_helpers.hpp index 9952eab8e3f..03a302eb533 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/libdatadog_helpers.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/libdatadog_helpers.hpp @@ -45,7 +45,8 @@ namespace Datadog { X(local_root_span_id, "local root span id") \ X(trace_type, "trace type") \ X(class_name, "class name") \ - X(lock_name, "lock name") + X(lock_name, "lock name") \ + X(gpu_device_name, "gpu device name") #define X_ENUM(a, b) a, #define X_STR(a, b) b, diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp index 8ddf412bf89..38baf59aa97 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample.hpp @@ -100,6 +100,9 @@ class Sample bool push_release(int64_t lock_time, int64_t count); bool push_alloc(int64_t size, int64_t count); bool push_heap(int64_t size); + bool push_gpu_gputime(int64_t time, int64_t count); + bool push_gpu_memory(int64_t size, int64_t count); + bool push_gpu_flops(int64_t flops, int64_t count); // Adds metadata to sample bool push_lock_name(std::string_view lock_name); @@ -112,11 +115,15 @@ class Sample bool push_exceptioninfo(std::string_view exception_type, int64_t count); bool push_class_name(std::string_view class_name); bool push_monotonic_ns(int64_t monotonic_ns); + bool push_absolute_ns(int64_t timestamp_ns); // Interacts with static Sample state bool is_timeline_enabled() const; static void set_timeline(bool enabled); + // Pytorch GPU metadata + bool push_gpu_device_name(std::string_view device_name); + // Assumes frames are pushed in leaf-order void push_frame(std::string_view name, // for ddog_prof_Function std::string_view filename, // for ddog_prof_Function diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/types.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/types.hpp index 51785be2c93..3c62fa5d62f 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/types.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/types.hpp @@ -11,7 +11,10 @@ enum SampleType : unsigned int LockRelease = 1 << 4, Allocation = 1 << 5, Heap = 1 << 6, - All = CPU | Wall | Exception | LockAcquire | LockRelease | Allocation | Heap + GPUTime = 1 << 7, + GPUMemory = 1 << 8, + GPUFlops = 1 << 9, + All = CPU | Wall | Exception | LockAcquire | LockRelease | Allocation | Heap | GPUTime | GPUMemory | GPUFlops }; // Every Sample object has a corresponding `values` vector, since libdatadog expects contiguous values per sample. @@ -30,6 +33,12 @@ struct ValueIndex unsigned short alloc_space; unsigned short alloc_count; unsigned short heap_space; + unsigned short gpu_time; + unsigned short gpu_count; + unsigned short gpu_alloc_space; + unsigned short gpu_alloc_count; + unsigned short gpu_flops; + unsigned short gpu_flops_samples; // Should be "count," but flops is already a count }; } // namespace Datadog diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp index baee51a7eda..5d3ef356c2a 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp @@ -193,6 +193,24 @@ ddup_push_heap(Datadog::Sample* sample, int64_t size) // cppcheck-suppress unuse sample->push_heap(size); } +void +ddup_push_gpu_gputime(Datadog::Sample* sample, int64_t time, int64_t count) // cppcheck-suppress unusedFunction +{ + sample->push_gpu_gputime(time, count); +} + +void +ddup_push_gpu_memory(Datadog::Sample* sample, int64_t size, int64_t count) // cppcheck-suppress unusedFunction +{ + sample->push_gpu_memory(size, count); +} + +void +ddup_push_gpu_flops(Datadog::Sample* sample, int64_t flops, int64_t count) // cppcheck-suppress unusedFunction +{ + sample->push_gpu_flops(flops, count); +} + void ddup_push_lock_name(Datadog::Sample* sample, std::string_view lock_name) // cppcheck-suppress unusedFunction { @@ -252,6 +270,12 @@ ddup_push_class_name(Datadog::Sample* sample, std::string_view class_name) // cp sample->push_class_name(class_name); } +void +ddup_push_gpu_device_name(Datadog::Sample* sample, std::string_view gpu_device_name) // cppcheck-suppress unusedFunction +{ + sample->push_gpu_device_name(gpu_device_name); +} + void ddup_push_frame(Datadog::Sample* sample, // cppcheck-suppress unusedFunction std::string_view _name, @@ -262,6 +286,12 @@ ddup_push_frame(Datadog::Sample* sample, // cppcheck-suppress unusedFunction sample->push_frame(_name, _filename, address, line); } +void +ddup_push_absolute_ns(Datadog::Sample* sample, int64_t timestamp_ns) // cppcheck-suppress unusedFunction +{ + sample->push_absolute_ns(timestamp_ns); +} + void ddup_push_monotonic_ns(Datadog::Sample* sample, int64_t monotonic_ns) // cppcheck-suppress unusedFunction { diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp index f9f7a3e9585..083ad1a655d 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp @@ -89,6 +89,23 @@ Datadog::Profile::setup_samplers() if (0U != (type_mask & SampleType::Heap)) { val_idx.heap_space = get_value_idx("heap-space", "bytes"); } + if (0U != (type_mask & SampleType::GPUTime)) { + val_idx.gpu_time = get_value_idx("gpu-time", "nanoseconds"); + val_idx.gpu_count = get_value_idx("gpu-samples", "count"); + } + if (0U != (type_mask & SampleType::GPUMemory)) { + // In the backend the unit is called 'gpu-space', but maybe for consistency + // it should be gpu-alloc-space + // gpu-alloc-samples may be unused, but it's passed along for scaling purposes + val_idx.gpu_alloc_space = get_value_idx("gpu-space", "bytes"); + val_idx.gpu_alloc_count = get_value_idx("gpu-alloc-samples", "count"); + } + if (0U != (type_mask & SampleType::GPUFlops)) { + // Technically "FLOPS" is a unit, but we call it a 'count' because no + // other profiler uses it as a unit. + val_idx.gpu_flops = get_value_idx("gpu-flops", "count"); + val_idx.gpu_flops_samples = get_value_idx("gpu-flops-samples", "count"); + } // Whatever the first sampler happens to be is the default "period" for the profile // The value of 1 is a pointless default. diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp index bc0a316bcc3..1e7ca1b0217 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp @@ -262,6 +262,42 @@ Datadog::Sample::push_heap(int64_t size) return false; } +bool +Datadog::Sample::push_gpu_gputime(int64_t time, int64_t count) +{ + if (0U != (type_mask & SampleType::GPUTime)) { + values[profile_state.val().gpu_time] += time * count; + values[profile_state.val().gpu_count] += count; + return true; + } + std::cout << "bad push gpu" << std::endl; + return false; +} + +bool +Datadog::Sample::push_gpu_memory(int64_t size, int64_t count) +{ + if (0U != (type_mask & SampleType::GPUMemory)) { + values[profile_state.val().gpu_alloc_space] += size * count; + values[profile_state.val().gpu_alloc_count] += count; + return true; + } + std::cout << "bad push gpu memory" << std::endl; + return false; +} + +bool +Datadog::Sample::push_gpu_flops(int64_t size, int64_t count) +{ + if (0U != (type_mask & SampleType::GPUFlops)) { + values[profile_state.val().gpu_flops] += size * count; + values[profile_state.val().gpu_flops_samples] += count; + return true; + } + std::cout << "bad push gpu flops" << std::endl; + return false; +} + bool Datadog::Sample::push_lock_name(std::string_view lock_name) { @@ -351,6 +387,28 @@ Datadog::Sample::push_class_name(std::string_view class_name) return true; } +bool +Datadog::Sample::push_gpu_device_name(std::string_view device_name) +{ + if (!push_label(ExportLabelKey::gpu_device_name, device_name)) { + std::cout << "bad push" << std::endl; + return false; + } + return true; +} + +bool +Datadog::Sample::push_absolute_ns(int64_t _timestamp_ns) +{ + // If timeline is not enabled, then this is a no-op + if (is_timeline_enabled()) { + endtime_ns = _timestamp_ns; + } + + return true; +} + + bool Datadog::Sample::push_monotonic_ns(int64_t _monotonic_ns) { diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi index 78351e93b91..2f466b62af3 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyi @@ -20,19 +20,24 @@ def start() -> None: ... def upload(tracer: Optional[Tracer]) -> None: ... class SampleHandle: - def push_cputime(self, value: int, count: int) -> None: ... - def push_walltime(self, value: int, count: int) -> None: ... + def flush_sample(self) -> None: ... + def push_absolute_ns(self, timestamp_ns: int) -> None: ... def push_acquire(self, value: int, count: int) -> None: ... - def push_release(self, value: int, count: int) -> None: ... def push_alloc(self, value: int, count: int) -> None: ... + def push_class_name(self, class_name: StringType) -> None: ... + def push_cputime(self, value: int, count: int) -> None: ... + def push_exceptioninfo(self, exc_type: Union[None, bytes, str, type], count: int) -> None: ... + def push_frame(self, name: StringType, filename: StringType, address: int, line: int) -> None: ... + def push_gpu_device_name(self, device_name: StringType) -> None: ... + def push_gpu_flops(self, value: int, count: int) -> None: ... + def push_gpu_gputime(self, value: int, count: int) -> None: ... + def push_gpu_memory(self, value: int, count: int) -> None: ... def push_heap(self, value: int) -> None: ... def push_lock_name(self, lock_name: StringType) -> None: ... - def push_frame(self, name: StringType, filename: StringType, address: int, line: int) -> None: ... - def push_threadinfo(self, thread_id: int, thread_native_id: int, thread_name: StringType) -> None: ... + def push_monotonic_ns(self, monotonic_ns: int) -> None: ... + def push_release(self, value: int, count: int) -> None: ... + def push_span(self, span: Optional[Span]) -> None: ... def push_task_id(self, task_id: Optional[int]) -> None: ... def push_task_name(self, task_name: StringType) -> None: ... - def push_exceptioninfo(self, exc_type: Union[None, bytes, str, type], count: int) -> None: ... - def push_class_name(self, class_name: StringType) -> None: ... - def push_span(self, span: Optional[Span]) -> None: ... - def push_monotonic_ns(self, monotonic_ns: int) -> None: ... - def flush_sample(self) -> None: ... + def push_threadinfo(self, thread_id: int, thread_native_id: int, thread_name: StringType) -> None: ... + def push_walltime(self, value: int, count: int) -> None: ... diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx index 9c590c796d7..5b8b6add921 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx @@ -68,6 +68,9 @@ cdef extern from "ddup_interface.hpp": void ddup_push_release(Sample *sample, int64_t release_time, int64_t count) void ddup_push_alloc(Sample *sample, int64_t size, int64_t count) void ddup_push_heap(Sample *sample, int64_t size) + void ddup_push_gpu_gputime(Sample *sample, int64_t gputime, int64_t count) + void ddup_push_gpu_memory(Sample *sample, int64_t size, int64_t count) + void ddup_push_gpu_flops(Sample *sample, int64_t flops, int64_t count) void ddup_push_lock_name(Sample *sample, string_view lock_name) void ddup_push_threadinfo(Sample *sample, int64_t thread_id, int64_t thread_native_id, string_view thread_name) void ddup_push_task_id(Sample *sample, int64_t task_id) @@ -77,8 +80,10 @@ cdef extern from "ddup_interface.hpp": void ddup_push_trace_type(Sample *sample, string_view trace_type) void ddup_push_exceptioninfo(Sample *sample, string_view exception_type, int64_t count) void ddup_push_class_name(Sample *sample, string_view class_name) + void ddup_push_gpu_device_name(Sample *sample, string_view device_name) void ddup_push_frame(Sample *sample, string_view _name, string_view _filename, uint64_t address, int64_t line) void ddup_push_monotonic_ns(Sample *sample, int64_t monotonic_ns) + void ddup_push_absolute_ns(Sample *sample, int64_t monotonic_ns) void ddup_flush_sample(Sample *sample) void ddup_drop_sample(Sample *sample) @@ -302,6 +307,18 @@ cdef call_ddup_push_class_name(Sample* sample, class_name: StringType): if utf8_data != NULL: ddup_push_class_name(sample, string_view(utf8_data, utf8_size)) +cdef call_ddup_push_gpu_device_name(Sample* sample, device_name: StringType): + if not device_name: + return + if isinstance(device_name, bytes): + ddup_push_gpu_device_name(sample, string_view(device_name, len(device_name))) + return + cdef const char* utf8_data + cdef Py_ssize_t utf8_size + utf8_data = PyUnicode_AsUTF8AndSize(device_name, &utf8_size) + if utf8_data != NULL: + ddup_push_gpu_device_name(sample, string_view(utf8_data, utf8_size)) + cdef call_ddup_push_trace_type(Sample* sample, trace_type: StringType): if not trace_type: return @@ -448,6 +465,18 @@ cdef class SampleHandle: if self.ptr is not NULL: ddup_push_heap(self.ptr, clamp_to_int64_unsigned(value)) + def push_gpu_gputime(self, value: int, count: int) -> None: + if self.ptr is not NULL: + ddup_push_gpu_gputime(self.ptr, clamp_to_int64_unsigned(value), clamp_to_int64_unsigned(count)) + + def push_gpu_memory(self, value: int, count: int) -> None: + if self.ptr is not NULL: + ddup_push_gpu_memory(self.ptr, clamp_to_int64_unsigned(value), clamp_to_int64_unsigned(count)) + + def push_gpu_flops(self, value: int, count: int) -> None: + if self.ptr is not NULL: + ddup_push_gpu_flops(self.ptr, clamp_to_int64_unsigned(value), clamp_to_int64_unsigned(count)) + def push_lock_name(self, lock_name: StringType) -> None: if self.ptr is not NULL: call_ddup_push_lock_name(self.ptr, lock_name) @@ -494,6 +523,10 @@ cdef class SampleHandle: if self.ptr is not NULL: call_ddup_push_class_name(self.ptr, class_name) + def push_gpu_device_name(self, device_name: StringType) -> None: + if self.ptr is not NULL: + call_ddup_push_gpu_device_name(self.ptr, device_name) + def push_span(self, span: Optional[Span]) -> None: if self.ptr is NULL: return @@ -512,6 +545,10 @@ cdef class SampleHandle: if self.ptr is not NULL: ddup_push_monotonic_ns(self.ptr, monotonic_ns) + def push_absolute_ns(self, timestamp_ns: int) -> None: + if self.ptr is not NULL: + ddup_push_absolute_ns(self.ptr, timestamp_ns) + def flush_sample(self) -> None: # Flushing the sample consumes it. The user will no longer be able to use # this handle after flushing it. diff --git a/ddtrace/profiling/collector/pytorch.py b/ddtrace/profiling/collector/pytorch.py new file mode 100644 index 00000000000..3d9e636871d --- /dev/null +++ b/ddtrace/profiling/collector/pytorch.py @@ -0,0 +1,211 @@ +from __future__ import absolute_import + +import abc +import logging +import random +import typing + +import wrapt + +from ddtrace._trace.tracer import Tracer +from ddtrace.internal.datadog.profiling import ddup +from ddtrace.profiling import _threading +from ddtrace.profiling import collector +from ddtrace.profiling.recorder import Recorder +from ddtrace.settings.profiling import config + + +LOG = logging.getLogger(__name__) + + +class _WrappedTorchProfiler(wrapt.ObjectProxy): + def __init__( + self, + wrapped: typing.Any, + recorder: Recorder, + tracer: typing.Optional[Tracer], + ) -> None: + wrapt.ObjectProxy.__init__(self, wrapped) + self.on_trace_ready = handle_torch_trace + self._self_recorder = recorder + self._self_tracer = tracer + + +class MLProfilerCollector(collector.CaptureSamplerCollector): + """Record ML framework (i.e. pytorch) profiler usage.""" + + def __init__(self, recorder=None): + super().__init__(recorder) + self.tracer = None + # Holds the pytorch profiler object which is wrapped by this class + self._original: typing.Any = None + + @abc.abstractmethod + def _get_patch_target(self): + # type: (...) -> typing.Any + pass + + @abc.abstractmethod + def _set_patch_target( + self, + value, # type: typing.Any + ): + # type: (...) -> None + pass + + def _start_service(self): + # type: (...) -> None + """Start collecting framework profiler usage.""" + try: + import torch + except ImportError as e: + raise collector.CollectorUnavailable(e) + self._torch_module = torch + self.patch() + super()._start_service() + + def _stop_service(self): + # type: (...) -> None + """Stop collecting framework profiler usage.""" + super()._stop_service() + self.unpatch() + + def patch(self): + # type: (...) -> None + """Patch the module for tracking profiling data.""" + # We only patch the profile call from the `torch.profiler` module. + self._original = self._get_patch_target() + + def profiler_init(wrapped, instance, args, kwargs): + profiler = wrapped(*args, **kwargs) + return self.PROFILED_TORCH_CLASS( + profiler, + self.recorder, + self.tracer, + ) + + self._set_patch_target(wrapt.FunctionWrapper(self._original, profiler_init)) + + def unpatch(self): + # type: (...) -> None + """Unpatch the torch.profiler module for tracking profiling data.""" + self._set_patch_target(self._original) + + +class TorchProfilerCollector(MLProfilerCollector): + """Monkey patch torch.profiler.profile usage.""" + + PROFILED_TORCH_CLASS = _WrappedTorchProfiler + + def __init__(self, recorder=None): + super().__init__(recorder) + + def _get_patch_target(self): + # type: (...) -> typing.Any + return self._torch_module.profiler.profile + + def _set_patch_target( + self, value # type: typing.Any + ): + # type: (...) -> None + self._torch_module.profiler.profile = value + + +def handle_torch_trace(prof): + NANOS_PER_MICROSECOND = 1e3 + LOG.debug("handle_torch_trace called") + events = prof.events() + if len(events) == 0: + return + + # need an upper bound of events collected, can be adjusted based on profile size. + # Sadly, there is no way AFAICT to tell the PyTorch profiler itself to limit the num of samples. + # We truncate to keep the uploaded profile to a reasonable size. + # For now, experiment with a default of 1_000_000 if nothing is set. + # TODO, better values here. + collection_fraction = 1.0 + num_events_to_report = min(len(events), config.pytorch.events_limit or 1_000_000) + if num_events_to_report < len(events): + LOG.debug("Dropped events. num_events_to_report %d. len(events): %d", num_events_to_report, len(events)) + collection_fraction = num_events_to_report / len(events) + + empty_events_count = 0 + + # earlier versions use microsecond, later versions use nanosecond + kineto_results = prof.profiler.kineto_results + if hasattr(kineto_results, "trace_start_ns"): + trace_start_ns = kineto_results.trace_start_ns() + elif hasattr(kineto_results, "trace_start_us"): + trace_start_ns = kineto_results.trace_start_us() * NANOS_PER_MICROSECOND + else: + raise AttributeError("Neither trace_start_ns nor trace_start_us exists") + + for e in events: + if collection_fraction < random.random(): # nosec: used for sampling, not security + continue + + handle = ddup.SampleHandle() + data_added = False + + # cpu time sample + if e.cpu_time > 0: + data_added = True + handle.push_cputime(int(e.cpu_time * NANOS_PER_MICROSECOND), e.count) + + # gpu time sample - both device_time and cuda_time are in microseconds + if hasattr(e, "device_time") and e.device_time > 0: + data_added = True + time_elapsed = int(e.device_time * NANOS_PER_MICROSECOND) + handle.push_gpu_gputime(time_elapsed, e.count) + elif hasattr(e, "cuda_time") and e.cuda_time > 0: + data_added = True + time_elapsed = int(e.cuda_time * NANOS_PER_MICROSECOND) + handle.push_gpu_gputime(time_elapsed, e.count) + + # gpu flops sample + if e.flops is not None and e.flops > 0: + data_added = True + handle.push_gpu_flops(e.flops, e.count) + + # GPU memory usage + # earlier versions of torch use cuda_memory_usage, recent versions use device_memory_usage + if hasattr(e, "device_memory_usage") and e.device_memory_usage is not None and e.device_memory_usage > 0: + data_added = True + handle.push_gpu_memory(e.device_memory_usage, e.count) + elif hasattr(e, "cuda_memory_usage") and e.cuda_memory_usage is not None and e.cuda_memory_usage > 0: + data_added = True + handle.push_gpu_memory(e.cuda_memory_usage, e.count) + + # If there is data, flush it to the profile. + # Otherwise, do nothing and the sample object will be dropped when it goes out of scope + if data_added: + handle.push_frame(e.name, "unknown-file", 0, 0) + # Pushing pseudoframes for the device name ("device.CPU" or "device.CUDA") + # onto the stack allows differentation of pytorch frames from other profiling frames + # in the flame graph. + # Note that stacks go root last, so this goes at the end + handle.push_frame("PYTORCH_" + str(e.device_type), "unknown-file", 0, 0) + + handle.push_gpu_device_name("cuda " + str(e.device_index)) + + if str(e.device_type).startswith("DeviceType.CPU"): + # There is a known issue with getting thread ids and names from pytorch. + # If we can't get one, just use a default name. + handle.push_threadinfo( + e.thread, + _threading.get_thread_native_id(e.thread), + _threading.get_thread_name(e.thread) or "PYTORCH-CPU-THREAD-" + str(e.thread), + ) + elif str(e.device_type).startswith("DeviceType.CUDA"): + handle.push_threadinfo( + e.thread, _threading.get_thread_native_id(e.thread), "PYTORCH-CUDA-" + str(e.device_index) + ) + else: + raise AttributeError(f"Unexpected device_type {e.device_type}") + + handle.push_absolute_ns(int(trace_start_ns + e.time_range.end * NANOS_PER_MICROSECOND)) + handle.flush_sample() + else: + if empty_events_count % 1000 == 0: + LOG.debug("%d events with no data to record: %s", empty_events_count, e) + empty_events_count += 1 diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index 9903cc29108..111c1624fd2 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -24,6 +24,7 @@ from ddtrace.profiling import scheduler from ddtrace.profiling.collector import asyncio from ddtrace.profiling.collector import memalloc +from ddtrace.profiling.collector import pytorch from ddtrace.profiling.collector import stack from ddtrace.profiling.collector import stack_event from ddtrace.profiling.collector import threading @@ -120,6 +121,7 @@ def __init__( _stack_collector_enabled: bool = profiling_config.stack.enabled, _stack_v2_enabled: bool = profiling_config.stack.v2_enabled, _lock_collector_enabled: bool = profiling_config.lock.enabled, + _pytorch_collector_enabled: bool = profiling_config.pytorch.enabled, enable_code_provenance: bool = profiling_config.code_provenance, endpoint_collection_enabled: bool = profiling_config.endpoint_collection, ): @@ -135,6 +137,7 @@ def __init__( self._stack_collector_enabled: bool = _stack_collector_enabled self._stack_v2_enabled: bool = _stack_v2_enabled self._lock_collector_enabled: bool = _lock_collector_enabled + self._pytorch_collector_enabled: bool = _pytorch_collector_enabled self.enable_code_provenance: bool = enable_code_provenance self.endpoint_collection_enabled: bool = endpoint_collection_enabled @@ -219,6 +222,12 @@ def _build_default_exporters(self): LOG.error("Profiling failures occurred in an injected instance of ddtrace, disabling profiling") return [] + # pytorch collector relies on libdd exporter + if self._pytorch_collector_enabled: + LOG.error("Disabling pytorch profiler as libdd collector failed to initialize") + config.pytorch.enabled = False + self._pytorch_collector_enabled = False + # DEV: Import this only if needed to avoid importing protobuf # unnecessarily from ddtrace.profiling.exporter import http @@ -297,6 +306,33 @@ def start_collector(collector_class: Type) -> None: for module, hook in self._collectors_on_import: ModuleWatchdog.register_module_hook(module, hook) + if self._pytorch_collector_enabled: + + def start_collector(collector_class: Type) -> None: + with self._service_lock: + col = collector_class(r) + + if self.status == service.ServiceStatus.RUNNING: + # The profiler is already running so we need to start the collector + try: + col.start() + LOG.debug("Started pytorch collector %r", col) + except collector.CollectorUnavailable: + LOG.debug("Collector %r pytorch is unavailable, disabling", col) + return + except Exception: + LOG.error("Failed to start collector %r pytorch, disabling.", col, exc_info=True) + return + + self._collectors.append(col) + + self._collectors_on_import = [ + ("torch", lambda _: start_collector(pytorch.TorchProfilerCollector)), + ] + + for module, hook in self._collectors_on_import: + ModuleWatchdog.register_module_hook(module, hook) + if self._memory_collector_enabled: self._collectors.append(memalloc.MemoryCollector(r)) diff --git a/ddtrace/settings/profiling.py b/ddtrace/settings/profiling.py index ad8d8794d69..94d71f1778b 100644 --- a/ddtrace/settings/profiling.py +++ b/ddtrace/settings/profiling.py @@ -92,7 +92,13 @@ def _is_libdd_required(config): # libdd... requires libdd # injected environments _cannot_ deploy protobuf, so they must use libdd # timeline requires libdd - return config.stack.v2_enabled or config.export._libdd_enabled or config._injected or config.timeline_enabled + return ( + config.stack.v2_enabled + or config.export._libdd_enabled + or config._injected + or config.timeline_enabled + or config.pytorch.enabled + ) # This value indicates whether or not profiling is _loaded_ in an injected environment. It does not by itself @@ -399,6 +405,26 @@ class ProfilingConfigHeap(En): sample_size = En.d(int, _derive_default_heap_sample_size) +class ProfilingConfigPytorch(En): + __item__ = __prefix__ = "pytorch" + + enabled = En.v( + bool, + "enabled", + default=False, + help_type="Boolean", + help="Whether to enable the PyTorch profiler", + ) + + events_limit = En.v( + int, + "events_limit", + default=1_000_000, + help_type="Integer", + help="How many events the PyTorch profiler records each collection", + ) + + class ProfilingConfigExport(En): __item__ = __prefix__ = "export" @@ -416,6 +442,7 @@ class ProfilingConfigExport(En): ProfilingConfig.include(ProfilingConfigLock, namespace="lock") ProfilingConfig.include(ProfilingConfigMemory, namespace="memory") ProfilingConfig.include(ProfilingConfigHeap, namespace="heap") +ProfilingConfig.include(ProfilingConfigPytorch, namespace="pytorch") ProfilingConfig.include(ProfilingConfigExport, namespace="export") config = ProfilingConfig() @@ -466,6 +493,8 @@ def config_str(config): configured_features.append("mem") if config.heap.sample_size > 0: configured_features.append("heap") + if config.pytorch.enabled: + configured_features.append("pytorch") if config.export.libdd_enabled: configured_features.append("exp_dd") else: diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst index fd556ef8770..309b6178c56 100644 --- a/docs/advanced_usage.rst +++ b/docs/advanced_usage.rst @@ -737,3 +737,85 @@ To avoid such duplicate log entries from ``ddtrace``, you can remove the automat ddtrace_logger = logging.getLogger("ddtrace") for handler in ddtrace_logger.handlers: ddtrace_logger.removeHandler(handler) + +PyTorch Profiling +----------------- + +The PyTorch profiler can be used to trace CPU and GPU events that occur when running inference or training on a PyTorch model. +The PyTorch profiler as it's `typically used `__, will output a trace json file to +local disk that can be loaded in a visualization tool like TensorBoard or Perfetto. With the dd-trace-py PyTorch profiler integration, we instrument the `profiler API `__ +to automatically export this data to Datadog for visualization without having to manually copy files between servers. + +The requirements for using this feature are: + +- must be using the `torch.profiler` module which was introduced in PyTorch version `1.8.1`. +- must set the environment variable `DD_PROFILING_PYTORCH_ENABLED=true`. + +It is important to note that we offer no different performance guarantees than the PyTorch profiler itself, which is not recommended to run in production continuously due to memory and CPU overhead. This +is an experimental feature which should be run with caution as it can add significant overhead. Additionally, please note that running this feature in certain +configurations can conflict with other features. For instance, running the NSight Systems or NSight Compute profiler alongside the PyTorch profiler on the same machine at the same time will likely lead to +errors as CUPTI generally does not support multiple concurrent readers. + + +Below is an example program using the well known `CIFAR-10 `__ dataset for image classification. +This can be run through the command line (assuming that a Datadog agent is running in the same environment) with: + +.. code-block:: bash + + DD_SERVICE=test-pytorch-service DD_PROFILING_PYTORCH_ENABLED=true DD_PROFILING_ENABLED=true ddtrace-run python cifar10.py + +.. code-block:: python + + import torch + import torch.nn + import torch.optim + import torch.utils.data + import torchvision.datasets + import torchvision.models + import torchvision.transforms as T + from torchvision.models import resnet18, ResNet18_Weights + + from torch.profiler import ProfilerActivity + + + def cifar(): + transform = T.Compose( + [T.Resize(224), T.ToTensor(), T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] + ) + train_set = torchvision.datasets.CIFAR10( + root="./data", train=True, download=True, transform=transform + ) + train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True) + device = torch.device("cuda") + model = resnet18(weights=ResNet18_Weights.DEFAULT).cuda() + criterion = torch.nn.CrossEntropyLoss() + optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) + model.train() + + def train(data): + inputs, labels = data[0].to(device=device), data[1].to(device=device) + outputs = model(inputs) + loss = criterion(outputs, labels) + optimizer.zero_grad() + loss.backward() + optimizer.step() + + with torch.profiler.profile( + activities=[ProfilerActivity.CUDA], + ): + for step, batch_data in enumerate(train_loader): + print("step #%d" % step) + if step >= (1 + 1 + 3) * 2: + break + train(batch_data) + + + if __name__ == "__main__": + cifar() + +The profiling data is then visible under the Timeseries tab in the profiling page. For instance, the GPU Time by Kernel Name metric is shown below +for an application serving inference with an LLM through PyTorch: + +.. image:: pytorch_metric.png + :width: 600 + :alt: Alternative text \ No newline at end of file diff --git a/docs/pytorch_metric.png b/docs/pytorch_metric.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b26cd57d566232c89320b06c09440067cce8b0 GIT binary patch literal 124186 zcmd?Rgbk%fqbxlvzwgxF}Bf_^5QDbdqsvSYT7dxNCJXhsqZmJeKsS& z=lw!pwg|DbDqK!u%@W!bDM$=T%yUUX+L_JDXxPO<36S*wX}+%IPwKik{WRLJ^CF|G z@L5bXTt6YYP$TcoLI|MIhILnrqH(Rgq6lA#4HuOYaiwyZ@v5R9xF6Hfxid!SL6KIc z4d9~CQZawruKOzVmC-6(<2L7|6MV(>Kn%QutS~j!?u#(bmwd~suLnYrlJ@zEC$MiK z52iV+V(%qg=ylGW&~_Z}7Us|$Zo%rt%)Pv_6rF+u*xQa&(yDBMs0t>iD&U__{N61r z0s1H}#AOI9O^qrfqQgWn8O}JKjjskKml&lS#a%su`tlM5kvvWA2f@?d8|E$;#=WPe zrCsV(VP5qG78Q%k)vA~DFsDx%$)N7?vyaRj)8%Ff5f$I8aR%E7@5J%icN z&Cbaf$ZY3G^CywN=}4M6nmAb6J6QtkD1Xs4{s3@x5~8O5#pwS&e_p2<(DFZ=>>U5< z7PNz`ziL?7S=dznlJ7)$dI;9L*dg0Jcz`PQw2gn!g(VcjaFV1zCS}{l7% z8#Li(rwf-*brFS;lN3`2!XBg|dp_?W>@&oL%e(>?pMyOBa0H@=9%52VA=K-J6;zCj zKSaCzUimp8#4~FAe9JoN0mwv@y&D6tcpT`L?HttytZYcw0bI>pE}J}D?~<>!ZMCvE zGGky+qyJF?n0!y74~Ye&;b=wwQ8G}Kn<3(qu(-b$Uu=5OQ}T8CkY$>GR>Fq48o>Vx zHSBeSS73>Igi4QpsT4gOwe~@f{YMc`f$8xGNvCv)`j^T{9N0g)`3Y~v&w0M%E+FUdRrb(Vus{hwmqj4~RZXLF+Uq0?6tsbVT)H^zQcEGDbz-a3HGS3=?- z6PmQ!3mBlq?FliU)+;6jS`&5mfv-Zty=`BWSNi!g-;({sawWz9P(hF65Jjz#8$}S86^Q6^Bg2b& zA&~CSaKJ|1O+#)@?RX+*;z@FbMN;Jb8dkS+mX&6C*Imv{b0&(uG8LM58ybx2t(*lz~b)x2e4ZVRaR8bhsn`vjN8KP{9L>il|FgGxWJjczK zqued_2rtWrif1sdObQRD41r5jOP>h_f2x7rQ~xnZP{`nje7IW2P9F?fRB@701-#C( zE~k-o>YENE8JD%HzK|R!w~dA9*wcgIA9VK*-GckbI)xnb&|x#9I$*MG4~j(%#&33<5SIR+K=*ACrah5c^fUNYlZB>);{m zvU28`PCz5`09+)B(fG#nSNTZp`4Y5QKxzICS?1YEJ@NvFnL>Hq2g$qU z(SF?at44Z01(v)RzN5M`Nnk8}`wXB|+~*zf>BG?bCnOJ($37C|9Yj(u>=J>Ne!uG|fg++bByL zo#F%*YZIvet#)eXn}t)8TfQqdU)3H*w-myHnVU6hp9r$A&Ya73-eBc5iIE9 zP0`&WpR{>V7u5f71 zB7q`_o2Qe<tuFQ*Z&|yf<+t{C(A3wsMeuSinUSqWHK*y5}-d3#aEMkB;oOMYh?i{f$MV zl7`aSCmZbha>M+@8kH5-Ks5X}fXjW3QHo)M=Nm+ym>#D;9`Q$t!Ji|~eFoLkRoc#h zTZ#-j8X)4 zUbnYNq<2H*m_2s^g;N(U79fltOgmI%Ez7sXk?5>sWRuVQaJmpsAG z$;?!BMsJx{AB@vw+m8{-3YE8E0WI{LUc{sh>!9g66SJvVTgGQEQK)yC}z>P0e#`2m({rKcM+cqI3=W(n;zfCx!NbZ*`<=JfTf;a6eTb^unmQVIzy^G{yY_jWm*#Q$V|Gq?z zxrH1Qvc8N?hmeFpb#X9MJ|7|^L3#}#e|k^R`bpvP!TvkAHKp#DeLUm|BOx1yx;5jo zm^@&SI|&OPHp+_^Qf6Wgn?Pnf3h-_ILVpV7}DUl6Pr*xSGY1)3yXKc zfO|X(4myF86tI(K6Mwq9mzvC86dG_}Y?h;GsB@HQX)Qi0*$5^V2?+xB1wGpu+)LIA z$J}_?&IY`s3V5x_R4&LU5HUmb)TkZkxILDnndWt1Y;CCf#@0sv(hN50ox@cKqK&J| zgh_f@%~}b7p>V%Mv5ek#T9lG*&a318W^2xp)3)0qoMbpzc;B^yTP88~fzN7eqA|Nq ziC4$Ay3D=Jte6Iff>_S{5Hc<1sZJ&#;X1)1EatsDPqg99Je2rW6Sx9~o z$xM;lXY&zSg$GCynX-X7%GK#gWPBb5>8?4eVZ(YQ8{|6$_V@#G3)J#FzpURopZAts znOm9m-~XJ*Qdc-vY5V$cgt<5i(Wo55OS897%vGXU0^N_yrYg+6Bja@Q(~4CTzhIrn z6Tj5wD=Q7gq3QJ2IdLL0_Hn~(Icu>u`jB3X%0UaXX$yp_m#o5f@y)lLj*WnjjF5q+4aC=_h6_;W}!5aKwVYqhB6zl`+ zlU-{z8b0#}c0<>$m^5D=asxLMpaS0atI_1U<9&y)ZUJeD7sN*Vi`xaB8MC%aRT?hG z%k=53Pv5&=9nX!$at8?)&((J^hpO_L{rE7i)OFn^JbJiC^xvUsnHlT2z>8Jl;JKt5%i5yJCtv z0!7k+$_;w_Y%sU@V^g}9%GYn@`zjDukz4gAn7Q{8m~6ni4t#RarOmC8I9+$A7MbPy z_tUmx_n@+8$xDn$Ec&xMdDcnq8qxQtYp#@>O@cd6Y-D!5`?yO>cuZc!C8G0K$wvD6 z5?1R+gii3-&sUpqme1l)@s1rKY1lBHDk`Ij=6GIB0UsVFbRu%Fh~JEpz}*idWt(oN zG~Du+>t{Dcl8gU%fj;F6CjMdA`e>ZePE%{YZF*SPX@NLpMA?t?)&sC3aG_XVZ4IO5 zEA4gXzq(y-ditU9Fz|EI&sVUXgx@gM8YP6uGm$0OGv*Z=lCDX3o>vsFKoHD4E@OZnz^}Inf@T|%{ zRBAQ*p^|+zoAJD+4_~B-S^doURQ?O&%2ymon6dsS|`co0F+?Qn}`lZ zhgG|q%f9pr3tSbCDPHR1DcJd%EoNc>9pHqYkgqN)&nVd17Ut&d`&w#lW*?q5Ip#)7vYV z^s3Mk`}eat43@B0*dfssPwzU-(S0yfaqwPGXP_Y&VJlvnKPBPk!@1j8WsH3W8Quun zBM9R3cAy-(%wk#W!%J!=EiC*|B^X4^wLh+CUO-oUHit|-&e6-S$1UdWf9yfT;!Z-AwS&XG7@sBLZ zr4k%&Iy-aP5H(9~S@Xm)Vthjj$S}qdJPn_=o)?peU6oECa7Rcixz_a{On1A-`6^-( z`|icf$rPxBEKrnOXx&7+8dw%D_w0s?`-9Qh&m@DHPOAxkT8?_?x;u>ZeNN$?`DN@q z|0Sv0#!Tzk{G3T_U^rud}n_1)Js$`3mP zJq%m?dOv{&=Rj60JOS=4`~%1wM>cZ?Vb2*$BrT;FbaFx(Eh}jUPc^;SWg~p!Wzs=n zt6~*|#n?yHGc`px6s@lfv7Tf-vP6hZ1HnS0oMl*^PwyU2b$>o=X5V@ciLb;crf^Ge z7n;haW4h_0e)@dvq(16JhQQanax{tX!5*fK!gf01HPhRzG|RlbH6r8S8JKeRSAkd3 zo%upm8K^{DR!~Q1-LE0t$Q|g=2;sOV(+H4JY+;N%46cKdc&Ay`^~`_ zymYa$g#BgR;GqRukd6YUyie1E%5e=9<8V>UN4i~Ke7pZjPkJaCP5I490A*x7~#C)Jf4zO4sQMO zglx4ExiI7RB5UbR4E znq0V1CC`V82+^;qbTjQ@mO^!yLzWZKg2UI;K1SkI)I_71#$T#Y{dvV@@zGUip(G}T z?(uXG?D62o4Bfc0NXzo#S9=MvUdYuLolt5p8NN%mrj795StPpdyrd^Tq7rIaE(W}( zreVfm*Ls@WFPEKpi(i|Mg~KU3yEQzmnf!SF@a9t(!9KP?y~{!1>AlovXFLh;i-N_V zKC+wFd1K%4ZC469kPh&uu24uNcwOkX8UpJG(`|ombtPgl3m4w^LdSV}edZ&P3hBxB zozbsq9)>s!z8}%6F+6Xi4iS5%i_IfQb`c7;54D8tcPrj0^Pf#&IY|aRxOg`|-QH1X z1U)kEx%PAHBC@_gdJ#>D8`Pms8^<*aM5ltM5I-+hQefb43P>at=Ltq@5{@m7PC zgz|*iuWf`cS0DIqVc>~_jA2O9HEjBPNRV)&5eQYRPCB0&oB~y*xAFZ^E}uTF7z0qu z`cjNT=B*-hP^jm_I|!9Vlbaq*ynh_;v$jzZi4LXnl(&ksJNYG8Gq*8Zf;ifqjIIg! z;g06%$)PKcA-XX91PW=dm%zXV#$q(uhmLcZitgt!GC_`E9`Llxk}KB50Rgi07Tkto zpBpe(JfvaTtm|2Qk(VAtnsa_6;=)GpU0h;vpQG=G-(03$#ODgqt@iI9TIw4Pt?95| zqRFteUL8DT+h&KC-<{TwJ4I+O))xl{IQzcUNg7=q1`r0!%dCXFmY2JZD~#mVyf& zjRczCsacFpKoIgr(z086wAz3-hSHVlrzC~q= zEz+!Y7m1McK}qPlunPfI?+kqOFRx2$kJN9Pwm7W7?BjCxKaLyPt9GH{f9uim_#Uo4 zWRS@^1yFs33%3lmkFy><2p`LT*colb%!-Rvu!$h~sGE<3!WLpA!nLvabIRR*WEXj% zKqoirou_3@@Ue{J?5(~PCETpxh#WV1-;O112-T|hok^OCep{Xk)IWOtX+B$!c$W3c5Sn8vGG|wS|#q2jbdB z{>uG`oWF8+azlc~YdUZZYgd;$Evs zVDp<*MIq+BTyV=$8V~e&?rEDr6T53n=%T(--tF-xea?mc^444-=M1I$nJi8=B?4yv zcb_^43?@ZM2ZBK4~k(=yD2I1V{nzY~VcB{+JDKSS;TZ>mC zc#>Indi2tV((ruxreo@*(X>|N;$KarEpeyuSZU(&8(;TNyk8?wSi>9>Mxr}tIS66F zc#l#?7U>f>Vj{x(@=Hq@TJJf+0U`f{#v8;ky6mV|6t6f})C3lTPY+xnm!bh9$db+3 zQ{eCbpxP{Y_skjVAZI9_=NIpYrwTvBGfTitR_@R_Mxe@5HCC_|-YoHcO3Mdza$|=l zwY7q{yoZZ#tThwr{ts-1NkU=4*` zr*=rvEIRm^sXw^Ex^AX8z@)o*=*4{XelzA52!aG~3xKDAQvJ;$oStD-J{Y>TIsvkT zWEr-SC6qXRp>H}$kEr?p2HZBG&o`bH5LFX9>W}Pm6egPjF#Jft9Rr+RYEe=5GGiOH z0C;#8LvN9H-sTvrPnW4pHXOUjfkJzv{?b!kj~)CwKU(!&j}H+&tq6~J!T5vw23f#Q zZKEcxeG~I8A(}ww#Nn0+i~`K6#h-wDf2xAqh1M!a93n*3iQ&0I&KW)?37#~becWhz z_7u^fDQVn}JzohbHNmRsBIDNz6~v&KnZl)9BExtj(V%=^cX&SrES91J|B$g2eV$FG zu&4tW6M5GoAXymGe+u<*HtJU^DyEqKwiZm_4K z%K=YYd|(A!cQ9=0_uC%4PRRRwV7pK3o04IhIzyzpL4;Mq$)#hj%l9p9rNzZ;AT>;_ zaYp^RtHTu{0lNT$TJ^CfiV|vu=({eP))YcV5R1pV_WFE^)*aHZ*EhgsVE+Rh08pqqqA2JQ=!G50}W2DH( zTwD_ZFeie{yP91E3BfDocds{&X8|f6D239%pW_{X-3qsCQld|NDQl=!cBJA9o|U7F z3eVSD#)`}71O0DjBU$e-P08+8%{87_C3UyQ6Et}C>ZyFLgXqUd{VRfyF?l|^&GcZd z9S#qvHh;ufej~_!iF*F_Yb)Fydn!iYTs1%c3 z8(IXfl69#g=zPTH1zs0K@DxPj3h)A2W+Ldtjn)+96%?XJbnDR4xFV82h0p-mVr-tt zt-@LAA2+Iz1Mi7J5DZuofGh!`fAD^2u*crG>#(EOyFeTPP8#-Q&md59Fx{f zOuDL=e3)77U_d?)ti=&Phe@Dk(wp1!TL0UEveyn<;$)-EApBdHJHr5M=_74dBY$HpR`Tz=l{enG~9Y&N@1dZUlpEVoKvF;b$F zDV%s;=A>VEoh(Mmj8He^xxwJT@0dpk<$-6cW~Ilj`i>`{w>^ZfTZV3}bu2qYOfCVA zXSrb|K%EiizKhLaHkh#ExABHo$4IHx?Jo+qOUUPtgM1#cSJBsk$HLl}iLyqt(*e0Y zeE@4p3jAOb?trF;Qvtlm%dPuUaJ@P9mJx6+q5xA8jtE|=zBjz;@keQQ&KS#8qG{rusSQcdTh@mX^`FVi%2M@gy^ zwsOu+yMU#AvQ6*Xzxqk z(tYIu_+n~Wmj*}@L-`(DQSh3!@amJfetwBz7T!28U--;fZQeC?IsU^N3gY$rBFCWd z-naNiTstsP8%ms#OQSZdX znW_UB);@6GK|}aQiTg7}xk6#rV5jGe5n~Lv@tNsBBti7dp5Bd@WQU|Y2f*iZ?XKWC zp6LSk@~v=G5qCPU3TtiXWw;9Q)psuuzdds_`+EJIV@6MxGxf|3kL-a!Z}1qgaNDg z;AXU>fd3M6nF*ft<5MYY`CgV)xVA3v$H*A1;LK$ksDJdh*f;}LPKT<@m`JNliEfxn zvZ4tR#6Nl)Wg8@h&<0t{=U=0pHMXaiU&Ih=R+eI2&zomjifAk|ze*GB~t100+N9T$Jy8G?o)!qx++#(J{ec+BqWKZMp zb6Y36mU<|7b&SZ+dGvbLni{AIXXo=^`K3BtKhza+5$fS@Jy&K)Wd$bW)oXT=^KK1a zVSu=p5F3NZny%uR8Q%z0pEc6>wI}c5WRrO}`w8GCl4vg;E;>1ZJ%5S_R|C~ETH>-G z4_N#8$CRI2>w?5wlmeRS4%goktjH79WCjxWin@bAe5x`l$Crmd)m}a12@z-zb$ue_ znZpIyj-BC?-9ia@L!+Y19XECbd;7ODPmbKeqvRt0PBkZ19u{LSz zwI1(o`UFcV@EsXYvSWRMvWml4kJ_Ju;7_BJJQ2scb@)AgD93IU4!0!JH8A=05|gRM z8sHK#bav*PLYd-c5Tq_Aczt<} z^8>+g>vvywWGsq_P;TT)4|ci{gwitY`$pUwvHj>IfiumgQ{C{Lyf)5?wka_R)~4Qd z%M_naPZW$-50R}ahf2QmW(WJ%x8UVg?1XRHQr~X7mJWv~?${PiObyfG*%utI_Uxkj z<3MhV;E2{QsmWjPusCt2i3j$>ucXN~xaih}?B5RW>Q5Gf-b*WW?Hztw(-pz>!FG+e zAN3-{p6saC_Dw>7TZ$2j7ubi#b-Ibo8Jh5_kNg#-e}U5m50hG_Y>E<$c1XGWS*o? zFBeA4JoVoRwVk(;Q|cT?Az?jJxKTT}$aW>Rf9fbOKA{jVdGzUuOnP^3mIkLG(mLXh_Q zhQwTx)BO#vE41tN?=c~*^Rr&a`)T=6fM1OsXbUPyEko?v( zVD`rqz#=1@Up3x(xmwIu^LxL^Y-(JxqlzAZ4;uBdei#{_f<)%4QI^YOKrFN`*X~r1 z6r1nBQnR#w!nb@^ar#p^t6vBZGfScIc`c#!IGl_kUP=A}8az__96NiqHC6XJ;Z!}H zXftTPscXtwI`V0Ml(3*{`LH{>^f234eEiczY0RUy;CxSxng@=&CkSL@*!rzhw`1w!eSK@V0IdI_9gkNZgfY-$T`%t3?JM!UDBn$X#n9elgS;C&%BhM%MnD zqo3mH`j&Oo@hUM04>uiK$8pJj#P9AGn8kNm0cLIf8&Tum)quyP|mYwLR8vjYu_ zfl$%)H+l{6Rt0IU9!6=!yJzvnjj&k+Y}YE3=^m$%UI=G(vq=!I2kDfQs8(wBIlEV| z$A+pXLx13PB-~g0R~Wz#9tJ;1{s5=_FXZkz6{c(ZAu=8VI$M87>LTK9pY&h`1>f7M2MAAs86cYf+!|W#o?~;=gwtDv1tS zC>HVm=j|N!QM!BjXdosXziR#p5Fq2dc6FY(KRW!5=>UQ!hl$8wq_ zMN3UhovY}fLGf_yHmPnoQ&RbyMQgmck?9}}CIGT-_S$r(Oej_uloPME(_q}vh>M@l z0WuRw0PY2P{X!=Y|2UQssU`Yi&SKa=?QumB-6azx=j(ZMzC)#Sq=!pDK=6gEO5;x> z_4lvu8%0X}w^5kXX5IaO&DAv;)!Yw=q2UVj)jKTF;l!MS)~pvX&2A3)PK4xh!9WqW zy_)WBJxKjKSpxMg1|ro7e%62*utZB*T!Qti++yMg9Tk;Wkk@U)a0=TytM|pYrr`|a zfM*}QcZPK-Ila^A1D|aseXzo|-oH$BkPN+EqiBBplZFPq5y1WKe}xszjhds?EKwlA zhDCl7_WZS~mRch=NJaSeK>jPQb5Qg3ftnW$@~5LgM`ChvmIp)6QNSG}Cv#&T&(toR z*LvawU005Ho&AM~_JWAb`xDb>XL=ry+O;X<`0+7;9d zHXxMBuTwr(tZ5R9>gw9JXV=trM{)3Q)AVHXx9R(H`c88AKL32xrdgh#GgGV+C}6bJ zpQfnu#*#M9VmP*#o&7BNOuSR=ZWW|o%0zV=^+MofOS-4RC52!P;uthykgHG_>u{BN znCsb4)p)RaB@&C=xJ&~fU`{*~a2;4qT^I^d)ZOwfzr+u5CK61>%O8p6F$8G~6FoMZ ztKTsgeh&wKcV8B%vLE0eBUUbTpj!@xhB5|NE5en99#GH3&Fj|V+G2cJ|~{IbaP*EwA&XqesSo7*#-Z7 zhv2zV>kGOmiw72-4<=iqE~REC)(`4Hkb7t=Pz4F&M)rM)4h=M-WEUl z$>li4_ZWS~U+@@%l%-E8CH~M_v;PKd;%s)@xa#9ihYrdPc>Uu<<73R(I|0yk3Z`c;T>jy;vz^`l*t*&?R)cZUQ?vwq4E60Qn^B=2I`ke0j4eFnzj z*|H>AuGCDa4e7~35b48BdqxMVTHe6rlgmJ^d=h@SmBWgVKCR|xidE6jr<l3(%7@6+KG;F=RkgAn zR}phL#>0VSuIuKR%KD${S_UNul-x=f97J4i z*nEBZp7hMRXborB4vkrryZqYfaW7BmEnefklzC4lThgh%-+&S-9RJGV2`vKqbdpYf z*YRYxnzg?~WLuY*REu&6I`*tI!k(I~Qyic*K3yS6*;nObhUbyG{dwzA0g424J`!Vi1a z&LuL=6KGZ$CJQBeR?ptLfkw86)ATW9L78RycxRKGN|u>I7j=9(AG5{7NnYrdA;GV5 z9hc`7Iw|`rR!_V6L*u%ip+6gWq}u znE{pYaPe}aX9gRG(?H1QuODmd!0Lfkb4lWZYRRuHFb{>gm#m4JL$M`);rNb6yl0=9 zROrWE>lI`W9Gx`lc7sYTPmY&rbqNhiB|Y*lZEGzk48Y4CS3fVzaB{mSkz-IC>8=w{*<_eN6kI1%cecAd9HUQHRZN3M$U2pbLj`*pv5!Dq z?Q&uD*1n9RXm2hP(2|+W^_GD3WpF}t4;RPk7GaKA^-C+!{BZU14-G8JL_4Pq`Q(Ja z(sa<7%Hh|Q3b$fKkGge?NDP1RvOL@py{31JiLCTw3pJDevdc$DCBUs{Mcwb(P2szE zdXSSw9?OpnWlc^ttaQ&w?MJ?GeWny#vC(gGnk0XMq6zmqYP`AaW|P@RathC;Hyq=> zL>I(8*e-r@Tty2cS4{(L7WYHre=_MZ%WO}-Eh`ZcM8i6_A@rzD0WA_AzhIyq7-66O zRG+;`WP}&&%)>crg5os~*`;2ce%ewAh)8CWYg@&TX= z8R&*k0Iv=))=se1MD7JA8c}Tx)zU5)twdq{$|FhMh<=?NNk$Po#*WzaaOMi}+4M*w z32@=ZCr%ZX|B?budzBPL|29vM05+Xvu!`LFC#};vl9oI_3`}OF==G7VtsjjDhq+Gx z^XBOzJgT)c*B-fj2E$Z##%do#FR3yh(jtxNMHFAZ$?ZFP?Q296;%0Q(*GN8OpF zrI&-8cHAgLT2xyXK^-R7eSRVrFD!tA$mbxj^0EA8Pz5X~eGZa+LE0p;hVva3?nJNi z?z;Sph1#^D10`@Wg5XIE0Pt%Jt=!w+>bAGXHB5E#6p=mJ`SpY7Q@f9ltMGO9FdUtgU> z=6i?1edIpWGdQbKP&;#*THnYZW zEDrsRtV~1=G!+K}J2+b(e|!Atpo{(>b^XKt${O|xiW_woX5Ox#2_?gwG)|^6Iw`RZ z;PbwkLh$WtRVc*0m5u0j_|oUGcTW89rH<_Ba5nTPCDZ4}$C;YS;j}tsw9Fu9YXPP( zWoz07-Np~OdX1jiEC!7^E$y$u`Oah?xm?%Mts)(`^xJe2-rj1hhL&d_p)^-c>EhMQ zc|w#DyY2e<0b1uBl6;LX#h%)LKiq!Ym+_s(7At%atbEz>`1Hf@r^HJq@`0&G^)aA` z`+YzP6wORevjhcRr8Pxt9F*YZ{DMG?n)wtWhZwZ$xey@%p+NeQmY(9-(j*E+6@o^e z<@DH|&+<^rss?TcJHI6vK8Bn%GDvKAL2^xv@NWI3GBP+8OgpdVU<2DdQ z*I+7{iBe0402D~&LaDG|g*}MgNZ>XVB{2KLex1p9E(y}pOCjggZN6tsYC7G5>{f}{ z{pB9`(@6k}dxGY>$s){}T$&^9!~O*PY3Mx^&gZ?m@TK}rHtH_EqB=DgT%rOZhHL;E zp1@6|s4duMXytcfZSQa-v_uau4z9ACk1A-&P5c|3oq}$y5?*U}RY#POZpCQdiE+){ z)-^y(?>7tGIgzkex4L0qsx~Ed)r-~T01`mq0L}Mwz_Wtzm9#HmA7;Os2T0WrmJqXG z7|PQJ?gz?yPO7cDjJ(CZ57zLe7PrWo1B8ndVW8Y|GA^|q$@5TP5Rx;fUXG&73)2~p zU}A?he_dKGAg-C4vKSjcdsrEV3{_4w)>`#LLtk2s7^^D2C){m<_7%0mL!h*Ytn)1> zrjU3G#|ld&5+Vecz+HCrH-tK^KhXl$=pT5ckINr_6}83GUdS`CDNe+x43mb=OgfJ~J3#T9lOYmUYQN)^x|Cg0ip=5VV zLq^UQoLNGvH@&qMfFxGKi79?nhtdrd_kJdk-C8dPeT?S;g<=de3q3z5Zeb&(y4RW{n)3rb*eGI#{0c)S^=VyI?)kCh)zVr2Zv>&clPFM{Zyc!Ii2j9U5;XO3L8wlU z zxXC2r{=>ufI|*oe^kgh-EpGY$eg6LA_W4LHdV0hfYxA38|3Mf|(q~;r)M%dRUpjOB z8K$SHO}2{cUpj+=4HY$vuUYl)R|4g@$_gE#N9qBWe~yqWr6@FQ6&@jR9@%ypx-4d8;4M377=^? zlRC%f(MFZc&?PGJ^e^8lpT}cZH$APpdT^llXHiMum(9_y+Hdj4Xy&UVZ^Ja%)GJ?@<7K8EB(aqjaN% z<%!g%UK`RWo5R~piz{>Qs>y%X%AEIbbf32qf7Ao(mSqYA1&?ZK#9(%CccwMvV}NZh zy7-P-{Vat3SDLrje`%I~ zOwc8$-n+_#EdKno!zBs_OWR)f3N~OU?>|OwE!g4+zj95k^5pwm z$S(#L&&av<^2Sr#(9EEzX_JNeq|hA5n&inP1-M5lY^T$-=r5FO#+f*!Z8D%$Ofo(u z=47s`FmpZ6l!N6uC6~i_ny;K@u`e-5G-i3*mT!XR$oeibOm8){Z$Hqa? z40ZV1{#T!1dzpC_GC{oCw5xW|fWAkA5d-$$?*h1#YY;9}s;8%YB}70@;yx^7mdJ*O z5(bP44UK6)BSh(TIPdu!OC^ECpi!0M^l?hsVW$coYkL&Y-EGAezRG=Br@ZGbYNKz+ zOQTM;C+rq@(x0zu8zkzwsUNJ>_aryvJ!No{CH3qx)d9e{4ZiXTnu$9op>!1k^nP&E!hgGEVLk_DOW2x_!pU{kDv3*1xr5NIxh- z$}PJUmazF)rk|_5(d%$~ZutWJ1frXTmW1^R*GLln$I1_ws@%tMHpXfFf2KZ#i>2w7~|UM{|Z!qXPk zo%^`s#pac(ySpXbz5~47w!8sDI0<E;#OJ3>!$qwxgmYb({I3p(qJrirj3LUxi;_^Jd-T&QldQdHr&d(+*sLgYJzU`G_^!w2xOYLwsz3Sl zl!j(%Ao=0$Z6cGl&0z9M#=*n}eU5CVcr2H^IppPjRg-YG(FZ-hlqcJA(^p>=Ip&4| zrPc<#pDwdfTI{6;35I@|_u=Ht^P8bxZ$;pbBz4&B=3~&{jWQkajKk`zVATtYaU?9? zb>GP<-{S|y3CZ`G9?riZ_u)DD(O0be`a&hvzi&(~cM@^yzNgsFgvpS)O){@qNLW+f z-t~ajF2H$f(9Xl_L%b{LLw^icwjG#MxNH=WM1T+^mBk_XHm~5^;XN}2pPND0<`_f6 z;jIs7{aT)2uyiavd!reLl(h*nHGWuyI7c#5kKys?Qw>(x-6L-mWt&;qfWz=`9hu@7942Tkw7POtI&hO+l#Z~ya>j-Tby5+rqji0nt_8nQ?@ci(N_`4JOg0~ueSnl!%+gE$!0;% z{28p5>nbZiJMXlsJimGgI9@UdsX8m|u&6>|mVAzv;onFqls&doGCUg`8ok{hoBxlk zw~lJ-Yqz$G6^hdqC=R8#6)CQzNO5;}mtw(PTHIY)++70%N^y600>u(M1pV?H`91GB z-{G%4$QXN!?5wrdTKAmuy63@o6r!x4M^PC{jXWO(nx&e!Qp023nWn_&-yI^a!WWT^ z>1ri;eUwLzCxqZ`kl$#BumSxB$R^Ih>vuH%h+1$}At1fhAQ2`8SBTFd4L9;>Nx$;B zq9Nm)y~(>TGGEpDc5y|b`vhY)Y_dNP0v(++;)SFF@8VjVj;}X37Qn`0erkfeH5UF?_HX&l^-Bci6BBa`Wq@H`4D-i72b(g4gy;r#iT^z+f?TS=N+0=IUR52tkqCW)e z5?UsQjVRt*EVYF%1g8;sj0_jW=OvY0Aw7>Y-$Y~m*Ty%22xAEgK%UGwxV}UbwHrpf zjt{vpBRBHO@b0Q7w?lqv6>w{Q@(!F5uXOSHg9V#vvo=OV)4SR|E=w+U8kN5y!Xel9 zhwW&>*b^BD+3QrHbNRaJ>GllyofU z=Z1agX0q4Io5kr@j40Md`9{jJ@lLS6T({g_(`F=mrb!MP9fmF(0nX_{Xj#2&Fe*gI zyTKPK+TQ4B55J90lv{O7%zQKORgv&c3{fS|6m)BWyeYvHt6gzg+bW4`g8b=6p2&7J zGJKmnO&ig^LVo6mgqJ;;!kMg5r6TcU(4i4+@xiyTH8c`$7*FiENZ5#E?EBpeS%2E-lx*5PS7*DkritMQ0GJ}G)`FLAM`h~x=vd=B1H`uxR zS&7SXtnBVvK0=1suvy`##ovRBJ>2qpS*|-iA7w)=?{E5rGij#~hcDT^ zXwG-!nxg4@0d*m7{hC|G**PmPw!2|Nwsbc6$<^u`DWyQ(v~DWp#D4qdMke2rCc(5? z;}nlw^dS+pis32f!SD|pU-!(2dAZ>U66fJ2WuHTsF=$#6G}w{m0j;xT&~fHsp1c|` z-b_GWnP2!#;U9RMDddr_oYS?Z$!9>}3xz0p=c52o9FCcPdT_QW#9Op&6_qV^-ML*)z{%er&JfOd1VYh9|{#S z&6WMrasiJ#dT*1O^-GF*_*10iYTR=X)J=KR-alV^2hivq*pyB#?Ds_*=nnsJvec}f zcXHVl!55Q*yV*v?ZLl96t26*csgEoB_9|W^*tVC!$RoafV*Pt)X=6Z zzY@sh<=zAx%7%|}fOg1oT}CD6Oqr1(JYWw)Vl^ZC`5GohnyrZnCCMy*`QE4N_Q$sb z-ZW>?=Px4_!Qsw>vFQ?$qH|`Cs~n>9f|UpAUCjxtS~n(02jjWd$Coj{_eh(RZ~0V+ zKZ*HGC(&_P4q$(>2IJ-Cb{XZaU^>zJ2O%g2)5IDuU&#FDs^1h~@KAlBPoAGYm@ZPY zR!RGY0KwOxEm_h=M_{oA!pBUad>t-d%v6lPdw={tt|X=UswNgDW}Q# z^qKN}P(M*+I#qb3Mg?oGp!?MI!}Z`pwey*7#N$6>+^I;2ZKzLUn! z$XU!+sbu`g@D6mFLmA%JVVw`qh&THjWo|LyKnP_l6{{j3X!*H_VK1|a50kA3KD{L7 znAU`<8Sh7!Ug3G({#7+k9cTH)8DLx|8^6=6LFKblC<0wH{=%S?Hv^pnEFVO%CaUe_ zMs(D`CnItrn+G3(y?Ao7S(aBNc*dRjL{%#^n+ocoQuN#F+TKpbnoK`OW5{oVhyrGx z*BH+%tX0%sV1mb;uDA9_Q>8$BP^?k;=VdmANrNV1w)K@cm9LNoA!+RsMmaQ5@4Jb)qMh(0mA^7jJph{rLmRxWz1lmbr7ov(8yIe`2QmE1}`b z!Q1+a-_Fas0WON#TU7r&4T2NsO#pk69my4LK6aHI#vH0uWurAu!nPGRp2}2WT2grW z5PgC!y;3QmOwI$m!_|9R`^ODsQF;#H%c20h^Emj`I^?J*7ohWs!bLk?#^L}XqE#!f zAO?3)_6CP<=%fGzThb8`-eH)Sa+A;BZCgIFmzdq8mtENY$AL$AW4~|;u0M)T)hn_X zzQxTVparSkLs?Pgf>5edA3N0Qo!-DD*DtjzPrSpJKkv{?Bm#R9O;;TWc;iDSv*;?R zJ3Cb$?;51T>p1ipbe`7RBz#1k+FtuE@AU+u_@r2kEm2ifeH0_T1joX$ODA*E5Q1G< z-O=pItUt{#x08d6RFth`+wP+`-NKaga*@Sror9qtZB_yRWJ9}^C#nmXN}ar~W(d#4Y@ZcTWAea=ma%Z*3VN#UD1;f>N-9@|YR zMOT%{gj#(W{}=l5Pah(rF@!`jtehW$B_lm<PFn>89q7H-58BaBRs{e{?~f|A8br{kx*toL%UbT+2o+*M3;7h1p^ zc+$fwy@pOK^-(uG!$!@ske+<)Hgd3~a&xj; z3mqR{zPzmbyMyp69Ts`+cVMXVRi$JJV%rydp2uO)UE@;0e}fV^p=hrHEqRJk;VV@n zTWJ0MM~ceoNmQHRrLfygCjzTvpdF_jr!LuPE;UL|gifADK=?ueWYNF*&1+Joe#A~+ z^KEeJzCusr50}C7wa&h)o4>1MsO4Qr-qHRMk7m^O2FCZi_->&=AY38E4-HC?izQ5?1#N4jh}K z5*~{udFI(J4A#l`AgtKm1Gj%?hfb3j>kIa`EM(p`zd_} z5LfbDw~%CpkOOQ(L`|OVCsqm}o52@ehm|7AKrdfWLR)bw@m<~ZQT;odxh9-G>6kld%Ulk|p^iiO z{5Q3O@;W|*6Xv9yO!{M79xq-U;G5w}?gIdcoXV>XuC9D&qOX6^^saNSXJte)_BxXQ z+I}_6l+w9*o!k-}-kV@~AFUhDUCuJdZ{+A|@ORKztdI2)QaMkAw=3R*XQWugv{?8c z$wbYgx_ls!J1?f1hGDUd;$RO7;(w~$YDztsE_`47KB5W#=@4*O>5NeTOEY$DH!H)Z z?@Q4X)E^*jJPRgMV6|ERtoQ}5j%Y48Ec+m-_U-9KtHPb{ssNl76tEc|PEOJDUjQ6GY zO;Z5%D4Ibhm8j1bOPsZeKU%J*Br>1F0yTz&$AC9Jj&N&)w-=pA|b4Waz!eNBHvE3ZX$Lw zFP|DP!O(>M(X@)Hzk#y)srY#J{eFHK0_NSIn}aHfM(W6*#k8x6{w_>giQj_e+3lT; zyt&nboDj8P2KE&5)dU+!=+JOXCcZm(DID$0^ED1OX!|FmRAnEr*A(@QOd1#W=)8tK8t zPbFOtsMuz|>0StnDB^Xm{Wy`ymMrY;B?0_Bwitc88wc z*4}D|f9W^e^g7ptcDupp78T+2zt%ndzFz=g2s)pkR!xgVjC6(%UFz8Bc_d}pudlkB zG&|=Q&0^{7iQm;rbl}a-v(g;2+Xi5{REvEFc_KGqIo!VRs%6+ z-1wN#W}6j-obO({B$wMI-a{Qp(P9~`&wGXOhgCO)phkng0G^FE@xo@;f5_%3uBVsw z65g`sQ9T|6uTGCH=v{R` zUxR?)-Fuh8m&&v~v~<)hv4hI*?t71?w)0nonGv&?0qd!+rP>Jbb`{}Pz!Ao#yh{v- z?+xP9q*`sZ`?@?#dJ~L~WD7 zlo`_`i9Pi4`jEx&S{=FWw~1LNDb6wjh>XM``95dezQxZ4Ue|AatB?`^5u80EZHL0o8?MgJclwf7_ah^{H`izJ z?h8I$zx(sG^PttVtqe+;G$5tya{1rB&)_XH zi6X)Qc%h^Nkt>?n!5a~7?n>cH2+At=vWL~l>a6IXaa`(I%;5)C+dQD^CZLOwi~&$1 z@CUeDS0j7Rx0f6MJ4!g_Ca#fvGSJlw%&C-V^0-^03nu1wlZJ|+?>}yiSUSf&9yODx zNr?nLZfJc%l>Pbg(T~cWA-DWO#BnR%n%R}WT>Ng^4SS=9cYf`5eVsU)(LDcsen7%P zit@^!nP9WeeZDRczKX7eEl30W1X8hAskYDauKJU?CWb$3hTPx1`{W^D9e47Yge0@7 zXFdZl&J9HL*mdcMS<(>A+Soy-%ey-d$co+6pAhoXAKhnMQY&DvTA{dw*v zF@5@$Ig9pkUanm_xs!g~7XFHTP+@Q=T1W^7h;*VVNKJ9F?f9O&=;p`Im!A^Y*j0y?dg_#lv6 ztvt?>Lm`xOf4UU-SvtnJs+3GP{c6oid+;bOp5frP=KTOKU09queA zsPxym&GhPpw1~6uwjs3isnja8uP1euIyuX(YM%`K1b>^~&B|wUg4d@`R9k6v(9c(e zZl3T}7C_m@ah|^P3WJe{;vxR zW`r7Eu%nbKB+1zMxJ#jSkQk;_+i3YcDLGnHgGRn}b8srzT;_F0Ablr$_$3&B%vQN_ z-=X5Go`@0UKM9SVQ^^7Z7Vlz@Rzx!+Ujk90pN{%5$RjTRwd3VmLyc_nUQG4@W1^q(95bI}^~ z!cesJg`n5h|3f|9z?V$~Oy)41(kEWLd_VfXrT!p3gt+ZNHBb68PoJ_j&tFNN4Aq6* zRms2k?RPI>Zun<+1ogz~f2p_#BJ(N6iI}_AOlh=>?^?T?Rhl_N;d+{R@Z7%@U=Fp9 zk!w-0>6gT{@Q%QR|EaYi^wrgfo~J%*Faj^F_A!vRb?bbt3zKvN5H~tH!Yycu)&^B`_Dq>prXPo_)z8cIUPYuLOjn|-rA+lET z!G4f_A#sCFKgAmg;X<@Pzt2C4^s~UEN9YkM-v3g*8!cVM41X`5Ba#t9_8G_jmU90? z@cqwim!dW`e>-xKZM0&_4v9U?dMiy>nDKk>}} z{^VbiG8ADXkqD{%XY~C00{<7{mXcS0>h%BpzkfWRCin#aKH0N>7N-B_`Fu|K7s)*D zooDp_@%^xo4`agT?tK0SB)oaf=iLe4e}AbU;{Tt)!fq#Td*~eNn+UHG!i>MINe2Rp)Ou=1dkD`d8ZiY#Eb}&3*S{`tC^~ZL8$V+;SafF16kAAxk2VAE*u( zMRw-_4;oG62s=c(n`K&(l-nw~4kJhx0pXsj(EE@A%=p#)*a(kmawN_D#jBHv$dJ(o zqm4FJPHMojS&-YpprtKzvk;j2i|P79lQ&Tq$@1r}-_GVNU#UN1GScjixJoF>lpx2MUJ2MHsD9h5l>A1ve>Ci>fxrg{2JT zNy%)r3$4-G)roO&eQsch7l}p1Z&z&-$4y_vg>pN`AAevMl|ayZ6NKBjft{32c%;H3 zDhvq~B2Ye!GQVN7^1)roBVv?$nSZ@4AQ*qjX*M&i-q#;3(q4s{uT#VxtoCV45Ai3) zFwY1YhV1&ZvXF7(DD!8I-kS@^e-RY%m@w3@=i4a~5Aa!AckRe+k?b*FDdyhwFX860 zb@MNYNo22$Z1SEHu9%ai){OQF6aFvfeTd|Xoh!4FRrw||3r-bR$wxD@srRe%d&p0o zR^}gnR|+ZZHw5TwQ38~|Z5YIDlY4x<%`hsAsv+P^14?Pf(M^vv5QH#2@WfuY!lpcazu+1(`IcK`9*CS~D`zBW2LQ zlo0sk*G2Z+km1!*f5(*uO$3sZId`S?vOGYJC3rU~(RlPqw-Jmm(p=1DOsf^E^{4Pw z9`6o9x3;+(Y>$zay|#+>Mlz);G}&o1SqRh5&OT-WZ9;QEE>h{^NlbF2^B~D@2Z)XU zfCZ}KVl()AwGs`a>?ei%F6mbF4vT>7>za*|Wv|Z*PryOb#Me@na(zBt{A>H8Bd%}K zM&@)Y!SPJaOB&C<*GkvqW`iN6M%7&3cJ|P88v|Oee7hSJyTm);Ub6ut7v~zfAk*3= z2Q8@p^7+AobwtiybARl4(e$wlJMo+Ife&sgyy4>Sxm&{t^mL|C-`xJ%?SNynQ;bip zOV6@D+y3{8I3`g}?(tim%&H><|8F#UQnXl0azl(4OkGrtJ4JlTA4Pl#*vEd#wo9Y$0IO%peRAQ4}R66K>GH9?K{ZQDQ z%aP7LD686x6N3G6d!-eY>jSUGqaW@GfFayBP@HV$P^07hR23n>ZPS=`C|X@EFz)bi zpg5WmgKTyoY#Y*_wlrkO`PIV#EY8b;-76h9O4pu1B5HcN9N^^)8_)J` z&N{*&KWm{NIjTsaV6N7UPUxT_lm{`73bQP&fVN)J|dk)Vv z_;AbYT4K??c3ZvP<&|#)y78nh6*#S2tR4DW?!I-V`h$X{go{7(4T%=AkdrLwo+co< zfa=Z-Mipw1CWQ6kk|c1V*fqISE%gWC0}~WRC0x)k-d%uwIHPvweyEW}^B7Q!u)eo9 zzEVrW#~?(`ueykfq<#ylp#c3fSXLxbcZTpwx&7STtHDiR(SCREqF+RFB&Er2{pDQ; zIKAfWt_!98flqYxD^BwP4cc_biOw^R31$AOqTaa|$4EXyS9`3=(a$z}TZ39u4;%g1 zjWa$U^ym68=@|X5#ysqj-{17le_d;Mh^ZdE4K2XcX|q;7A}?^Tk12?$SqJbBjrblo zS7aYoKKHvYYYQ^?Z*K6Y{KNxj);osNurhU^-BV3CJ40OuYkcX~+@-M# z(0XaP_v$oHG88c5P9?(NL_SScj+eP@k!a@~=$MTCEpci~Oa@v0Z( z*Dizd`3rq?%kMKDRQ!HnEsrp&brUSvniGLC)AbeMk8k6hHoN2zNNjbATFc7CsbmIa zTbt+ZCY$ANaycJq3_Jb8mGT6}DeBxcO1`{ePT;6ze!U@XzpVJs^RsWnk?@E8I;qh} zpWNc%&}%IZ#-cRLPE}oxB654%DQS!pY_{oNQnQs;J79Uej&Fpy!u}CE1F@2pa%n0D zQK)qv5Wj!C5VSr8=>P5Jb>CH3i?RRL;beME%TB;~h_qtPmvK_wg2;Hv*>m;MGR0_) zxq1W=^Zee7BDc#Wc+-2j3{j^a;r?xu%Ul@y9Fm+I%Pi^7_x23qVUls-h9o{v?l{k{ zCOWP5eeFoTZa;$cl}n)xZbuMyDHu+|y*9Xsj#o6QT{|5_)Atv# z(x%X<)7x#d(L4$SB0XL%lDqkIg?OJNmrPouPk4kO#l4n_o{G;EVrF@+@Hnk_g*MVdSY|*Suqdg>s@Iae(0C7Ps^6^O602O#?|!Oyt(e>Oz1pue^%Kw-N`G~~ zMA+o>C_(8m{0D_fvs}$vo#BNe;v`6bi0Q;6(SMg=6RQva0x0qQAm*S~&e!SxG4FJ7?k#UAzi$GKkQS~>nOaMG>LQ!{LqcXVSToz? zx?Jnv%Hxgmx51Ik*cbGw=|pR7!8`g{UCTFT(d6b^k4GH?_tRPxpjvM!duIU7PaS4U zIJ^H)If9%m0pi)U{q*YZ30!{;_-uR<0_le&L?7Pd4!YZIGX54<7t9k3Mxv;bd}Pbk zE?6<5=Mx#TN57tSvQ;nL z_PxNQ5cbPBnk)M%g+*)yiM-8hN#0USr1AHuP-$WH^$_68HR~0Nh6l*jjz|jmKl<5T z-JZ`?xta&u6ctFy#GUz`cKCp(wta;#HxCfEQ1#f(b!OrNFt3+X>;DzTn=0D%El`7F z+Tq*W7ze5%+7!$`MhbJlVWPHoJa#IjH4WmwPI7%cXW8$8o22q{h)Dnjz*}eA%P=5TSFAWwbpX}_uyfivXq=hg&w9CDFUT%a^_eT+g zBAQhFdOM6*CuU|#C);W@GrA`1&g%a%Kbi(i{M_XC-9Q!`2rc+KS*-_3Jet4SVUk4~ z8*lV@_+30&RZz3V6s0|X*=;bqVwiT{}B5hByt?I3CO&eOa)t zcYzg+Ha&AGy!%;sJ=Oj-Wt8ChjHbN}$ASHCOy6|eQvis{Yl7h{Ui5vxnKnHtWk?PX z@ZAKwbhpWVoz&O;7#UZW-D)m}HM9DfasHi1l`)XWTeEzOT@z+2rf)%=A>WZ8R0Zh$ zjE`!2(+L`Pl9YUU_p?f+kzTQ&R@AxV?U}rGO<7agb1xVpcbiJbl>7dXX| zC!^b<@fCu>v)%spmCgPi*SQNs%wgGZMbvMsXiBU4>O6c;R$|pNPhDOGB1C&eOOfSpsyMw4d5&DRZCTjb!6lORCMBroWf`o>P8}v)oVTs|(JK zfm+Vw*W5Sxztt?v2wS(p^{Z6uf`8+N-^t|3)j1d`3-+SCB(%E$_ZC?+nI3Ya2+5Eh z@mc(oi<|JxR(i9vbD$V)^;~w*Gfx%DDE)o79xdX^vBnI`{n$W1S3T*ED`II%zkF;n zR+P!$Zs%qx511>O$M8(NQC*%UQV1{C4 z0WjEBP<{r-^NF z!YMmzz2$c_+l5s9grB^%@mR`p3kz&Z&Q!&bOWCkiTZjI z!p&o`H}gV~OvoE2SJXGTRugXp=p980=yfOX8iG8cm&=7W2*>UTUFVPrSqjq0;^&td z{V73!(y}UpfOLX!x*;FaR}C^u(KtD&lefaS8AyLZihWD<@PasY)DaM>Vg)W~@l$>g}gV|^D359(2Jzu|B@mnK_cLl<( zcTM$rr3aM*1Ci6O!lz)@^KvCVw z{VC1ZuV#O+PyVOE_Z?w?`xN=tKv=iGUBP?j)nmDl8MUtIC3s7x#Ux}F7mxmk@`Qhv!qPlV2GRs^T0pTO(g!I;b$pcftw z`()ipw?UqMg>u$;Q&r z*DV@p8V8Y^yR31_NWp4u*DIg&+O0lm9`u$VPA28H-)!EzoTdJ#b+Q3WzgHxg^Hg~X z4H2>3a|yt$0e9>91b||+`@43x0#*KU+7zI1QqwV9^+q~pM_%h!fqqF9~R+wGnBR$>BZimVqW>`csV9>O2;bd(jz=D@a6 z#%?N;C?}leK29``yMrZ2Z9dJNb1xWQ()oBER+R*hD)+3ouE|TnbJQ=~T9jcmsQr)m zgq61U6YB$2O10J_33)5ccXA@u)9B>fei_VtMj(P$Q=2(FjxU>?&IJ_nKPurgcpfc; z-s3>9mUP?A(BzT@Q#&{`3bCI}#owT~>Zt`pNd^Vu5YXT+yaI$>P#WL|nxw-UPJZx5 zQe~`rH+c@$h&)xuO}d#@G#(R%L~GP)Na*IC_YGi~?WUb-{8V7N?&+r%cWW_Q4?Iga zATvf#9V4Iq*e1XB{5!;{Ra++mZga7%3VKKQK3r|Xk8KG}B0uCc) z&F*00fBd;x1Cr`Z?Fe$fcbg4Rg3R!AECwfx1Fl0*n$Yl}9) zr>Lo7Nkz&8x&Fc#!nWDVpqfjnv@_B&{X#~sv-jl^k!u38GdG5%51(Ewq8=b2c-3)w zks(64Z1cvvET#LUeHX%K2PG9_<_u!p*!rzyWyt-e;(e;b9&4d-DeEZ*b>=27&I#}j zUgRN_POhIVdhSYZZW5I#Vt2l?obL#&h&jz08I*Kyv?mj20w~P;nF2M}gHQoCXI8Sf=e+YR z(WsCzoYvb5Yxpimz_`j%ve|+jLG9OZbGEDuMT>zp(<>Foc>NTEpXFvg3US=r_5@*8 z6-UNV7LN(@Yap7N6tg1wF=TFvxgKKsrl3YrpHaMxouQL!Naf=8bwMLf>2Qa0>6iI4 zde!6{iKOlJ&51wY{vc+m=zQ4(qe+3j564t3ib@2?jefQCS5D)}B$XY_`&7w=DeQma zN{zGeQkUC$8>aJqN4r7>mN}XvnvxPfN^M+`d|twvmIhtOp<8 zq0*mC^fqFl=nW^)n~vJ?#FaMaT9VC`wl*yE?HB=lGy}wHajW<*&kGrp8i);h#N3dH zwMpm8i(>>*y-f(!>K#bHgrUI^$nc9Z6Q`bgj7Rp)!gGbtVzNnsLLDL8coYobimms! z(Lz<%@8)oRP@kCzTg6%v+y%8!As_P{-EF->Lj#(0cQU`k%#%1+&Zhpbw_PZ+#usf> zBj;PYwaoM$^|v6>vuN&5cV3e9EiSw2Lkw(wcB6?dcxW7Ih_(^3!+y0Ohw%+yp zg=te|s(BCWeaEu2Vkh5Z&udV7>8i)*7?lQf8qR#&{mZ38IFhfbYow~&*~Y2i;R5cy zqnTR2Ew^WS?Sn0Xq4^ zL|WD3f%V-b^mJsTJK!r7?@BJxNuq5^9{~-4~be>Q`$ev9GzUux}n1d-4n;Q#5fmUixGOb5c~DJUgwH zbPmN}d+7OkkdD^3P_E?^6vdm^AGKF%Yg|t-LxQjA*bTgGPMaQ1x%G}+wwQdQf|^zF zUmQaoeFqGcYVE-*83aEFs-E;_isbr@0;uwtd_a^@x`;T)M)BK#l+1JyHLbd9@*kcK zHyynuN|ytQ8iG}l<-uOTMA&alsKujORLscI;II3COa}rzRc%`q`kJX`13Qy z?Rskr!TJ?D?+uIj#TNcc$u%0~rEJWXOuP5wt7pOTQ zFug`prVp1gWip?O>8sin&5lXmJDO4MAsBKtFM&J~4FMVI?%}D>oReRe(~uIozB4Lm zmub^Bmvb(bwElSlckGWU2?;{My>u#R)ETtc_Ux>{@QZsW^exceA>wAyW zYkk}!zzLkQckTaiVzX>Lv01dDp`(e$=NWb07;G204Qi^Yf*t9lBVGWikyNEju)r~i z$q{v`*MH)r0u>)O5>}di^n~Io#Mh_%ovspN=S-(O?eSkbKekI-esC-_066+>yt|ZJ z7Ajfg`Lx;fE!^*EroF-S03u_#!wYo&{Be;aSK!a((gcy0d%;;VPT?=8oMFqwmcXrL zV+k`CrDtU{}A)Q)I2uR9Yqg;}-L@A$jita)3q)%FEne-wG4#j2oP zF_E^W!Iack`Uj<3OLfdGwL10tA2;PA*)ZHnAeQPPEHu z9rE<@W*&L>{H?G`U7QI1*}C^>Z|DK*j%AxZ`6-{#mTmDLw{e;%VkC(`?hmLi)XtP9 zgPm?MRCV@$9sL>b>mo@PCjUy_bBbYZ0tQF%fAFnIn}UZE*`g(_bvj)nEUO3oK76T( zyeC|XXt3sFI`aMoF8DkKAHl5klzERJJMnnjbbcHgzPA2Sp+ z@KNl3D>3@y*WbdBqh8t`f^z-FGC{nL10JM;cI6G7 z>8zyg&d4$&Pmu?8&KsUNLP(P~OJ$aFrV+KO)Td@`9T6Dki7i#UFjR;kslc3ge z`-lq_CzT?t=Fbj}8zmvTrs6316n_v6V0uLrjC;;7JKt>~aB$5Y90a4C!H!UHITygF z*D!$1Yv<#O^3b`|ZFu}aJ8g4pVQ)H%Ay?h69KneJfk5PF^nC#*YkP|K1-tEGCho_8 zsq=IRL>HAGpmV5uNxko866 z{-&@#x%8sTu|aNG5WVxwnV(Ed@|u7G$|&QX3`$JWUo*5r#?L?dG#>fUi|VzlO%C1} zQdXtgVg2?*L*A6Amb~IR5W1oaxQ#u{j3I9g!&PCbM zO%7~z(yVu2zxWhdEaaOOgcwn~xVo*jh0J)DU=E}1qL3)lKpF@!`L1#&rKnFB{+vA4 z1QAj z(kU%jp(1+jgOa1*XE2BL^2yf==dnfGic=muMjcGh-n`zQ1Ma%{07?otVhhSdsmuuY zXPm!?d7>MOtzBaHVfRz`FDE2q%~zIlWK!2L9Ss&RnDLuVX?(#5^FUru{A&7G)%NH@ zoXd5rT&%L|e%2$-12A{DWOe3ly>VLTI{KB-mydDKc zE93X{6E?XwUr?|1hviI|MSlUtP65({NaY5c{H%~nDEwadq{4o?6dc#wOkgqEA^?1J z%w}F|DPzVU{gq`eq0;u@JBl9JmFsT9*GILhy+e7zCYAa4ooBiF4nI1dL* zMFe0=Tfphs{ALR;e{Tm(W%74R&v-<9;+dr_*x01!#lL;~us`pqS=yqO8yX z_%VN+_bkOqi)yZ$0e9P!T|ArqLC}4pGR$cRqRcu=%V<)wv8;(rgl%HGadUK~U62~P zwPR6?$oGPuz4EwyaVs>}qs?_5Dv=j^nT9kF+AmX?HMX9_ zryNDc$rjX6zPCrGtD_)`bhxWc(i<8_^0L7j>k7+EmPd{Ns~lnnlkb(LBfTr859T_| ziLa#!xt4*kN(a0H&6QZ0W3g zE~VSvpi*9q@awF(+R@Mns(UYcwlLMZB`?=3oTdK>iTXQwI{(Qe^g3hhl zgUH_?-(Y+(SuBk2mbp=T*~j@MA{>=hd0cDW#kxXmVUNg(4Mz#*a&g*wK=`XR>JOe2 zKy?#PEy9wr38=CWq-8ql4q1v=rI2nv&lDJoHs-f388_Qbi%%DsL6^d%1okfW?=76z z2;6EcvE&_4GI(M`Y&v*L!n_C1cl#TTE>3KO$iKp_mRa&HiP!B4IQ=kLNrBpfF{W8> zVhP+BJyu>rW#Kf5FE^0t@PWUvdY=mZkfnNhH7{!|+qyNE>OL6WVp_U2GK|YZG6p>5 zQ`@ZZRaA^?3y6LMMa5HsR9s18-0)(-J4TV|uioG4doINs#vGHi3^v|Z&Dx%i`NA(! z8|)4d$YbRJ(~Zk?MY2Oft@NYztlqoJ!=Ju&`qg|@rwUiU1Ea%Z11Wt*lae=}5OBmO z0J;RhD-c~<&75WU)^4Z35Z#M%J0PKGjTM?Ij42bo7^*1vT+`*7Qt3PBxycyKb;l-4 zoKYbB03pyB%k=EMh?9Vqss4JYg+_zFu}3mn?wij3;J}T>-JJT$6%84PE6DL{zYt`4 zEPzov3~=4eKyP*zWi?G4aQ(9~6>78;&&A1L42&~3HZiN^u!ZWFfL~to-&t_ndQ+x% zl~r*zc#sDt6AVob^=s+71jOCQoTt9VM%|h9)N{Bd!%AVUd_QYGMYM=YsT+rRmk`&& zRL3IOviX_yRh5wdJ8DRBkiDLoOUqEK<*q5QMll&4s32{7K>b@AV_O-$ zRVxc>wa~;SRxaq4ig0Pd2l#rpqa@2#Th%9b`zG`NM}!9qxINeJ#P3GPk^!QEXG z9D=*MySsaEcil*^joaofI^BIvpTA%4eY!7rYKm z)?&*?t%giGxRMP#)_pA&MQ$hFT!pxH`^-=c{%wJ%c*ErGo-_WM>YeO(CRr^EHSA9B z;H!%gQR6y$`7BM7@AD8fG)_(s3-Mtm=4qr{CFeLTmX5`pd(T~Xl!wDdky=J=0!M3G z)*3(nltp1R596bC-7x7KYcu>W37_j)9NtMhD;V@{IOW`yTkb>r+b{Ud<&?Ey2r6mQ z3->qex1mmNIkM^(IaO4yvfO`C9hJ^d-1zZ}Kflh7x%vUOXKYWrHCjryZR>&_b!pRl|7tBz1=@^y5$;3 z+ua*9cHe-zv?SP94!(OEHst0q5Jj02(S{~FQu|*`|9asr!w8R zZy$kvx7bvg>15lWQBIdgz+Xx-nay86KC@4q;x!HiJ)bLglgL+J;)x5X{)h;d;ze~V zV7OaB*{&t-xj3s&M2&_GgN`X4I|bpELI^^PlSnV8*l%un4d%+Mzu#M$<^IIlOnoXV zs;A{2iH;uoJaryl4X$?}UU>#+d{V>>agNlL=7t0!LK`B9D>@nmO1O}(v3oO>H1`Fc zF&ll~%%h|J=7os~BiOD4BP+k@)RcHP`SY!r;NGohQ2VJm%rQpyMp;q-BD56DH;QB&dEIxg=|&<1 zyKteYj@XX5D_oDo`QDppDX3_m1m7jGd7Q7UOEq2j7Hv!MlCMd_=-qN1Ls{RgC^WiX z5wx6MP;du`{4wW0tUx9WN;6T9;G0hjxAEE_S3vrx)#jzbQU2Y;(5&(+43FD6p9@rq zv#UM#Rc8-zDZ?k(JUgJ@#QMf%%jYEQn}fBU$RDq=(seN%6@>@$H0Iwz`E6zbWFMPG z{DWyL369eX+I&C+vK6PW$>0``<>E;g7zu@OlGs|El%Vm(m&?&#|3}=Te3md`ZjpDKTP6(=Iq1?Ew{q+fGlzMx0wDj1lbaH zz{?q=rG~2zizN?1UQ74A?IJe42fOSCL9#hSMPdi?xxrW;-?;YM)CAu;7-}sRHvE;rzu?fT; z&gVVcVAOOLR~r2KSS|CeIgL2_Dm$W+>sz@a|cxw{Vy84{(|JU z=VhPvODO{TxB7ts1FNG>5&!+6M2;w-z1I;>6Z}(oOP~0o9UZaJ5VqYXYs7y1v;p@& z!c(_>0cP9I3oV8trzPp6BHnSZ^i8VTD z$#e8Lrjmbs!yU)P)aHH{`${bABi1WnD=}-mLR7xia4S9$^d8p&on^Y}i(w&%>xKS5&+&X zJphaEr`y@<*dq?9gPb*pgWXAYLiLO(idz|_qYfY6g<6ex1g~~dyA)FEi`YYy*2L^D zzlDW}1e2Bx4vU)xI&o7A-JahEM)^V5wVOOrQrn!Q3lwrP0d@sh^D;tKu2{&s#&YYs zOJYJkgLe-TRd^!7xS_ECIEFF^H|vW;RHJMn=nInc&`m~G-`?=e8AO`Zc%xgfz)fv1 zx($CMktvZT7HDVbWMr1^>i|TqINg51@i?6aw0R~)mznEH#=WsGXE6i-55}iKO4VP- zn{}yI#|7_ZykXjAO~BxJZ26mCf%&WN9%o12opv+Y$`u>UAXIy(yM^_-rA9F3bU9h5B-F*LqeZz7pwSjKdu&E&fJl~u%MvAgj=AB;mIp71XFs!ql- zncF?Kneam%wrj2261(-e#o4>^%^g4Taf8 z^_LE-IE~}LXH_m)6#r19^rod+r3awv+wI)Tm|bhnIx@ZP%EEaJ&-R#If^2V&P*YTy-%v7Ozp^ITgN#M02aXYkXXTzPX*R zMJ70dHz3AUxOY7{A$PtL?^brDV>4TkwB2FMeg%J)6lE})s78IfL!8lUvvrMRGM#~P zbA2s4-U&}_@oTQyXbU4Ykh=4JstQf{gri|W;* zGU2&5F*h!9BJ9 z>DTZ%5;2jrhf5`30i+|aIhX%X`N!+F@l7g^=6XA~D`l6fBc9Mi_HXI;1U+i8D`hPO zhO6Gy=Vb}d(N}q+spB!Gv!Oj2T0pJx^M}45kD}2eb)KWJ6nqx%jiY=zotD^%@{N>O z1V!SC_V84e7zjR>9QA~=&DXq=$HUC`pr4Z&he=c}=k1;#NP|Y(Dzhy3U`>z3vE%b# z#0M0h-%AV$ubVLJBb(38rzG(lsTa2A=gHEbHg{>mIMm;YTO;9lvp4TJK=@$)ah=;z~I+; ze#aS0H3usTq8RHb*x*Kq3^rwjt>Fh-o1$;iV=rcoc_?@52j-gjXMNQT({WT^U*Dw}n8-{%vq!?6I$*QBk;=I0 zdi65i4w4(nA@RVduFx<+xqEl1lXL}NyHUETqk92&;k175yw@|N@`BNVpAlMBEG4G* z!bHKpwq$oCiH+qte4OR;((BtyYuGbep*Ya)`wei6=~RK1sfCVaOpWL9QDcCvnu}6_ z@O9OTp-YSTiYVz69>av;lEnojZCBnU0J;$Qnw7`10)rW)+9q7T!r5!@HZ*B)QwUTu zP3DBhZ+(1iZ@Pc#E_QYYg{+hSwP=db~H$mQe(^k)*R z(C^9I5DAw5R=I6-)!JfraB2Po_)FOK@x)c=JtxQq*c{In?mm=gR(%I}@4Hi4Q%XMJ`o_L%l1o z+DmFEswCVfGnNb8#L+~B@jf@;D3$3#6Oyfw#X6&dFycZj_IPHYXC^E86>S`my^UQ9 zw-OHi_vYRBoWr;R;#rL~PA;9{CeqjQXL$J z(%Lb63Z?0d3J9j0oR;N3a2S51HQGaBC2?t=Lk)R0U14dGs6cr7bP!$+298r+RxO|* z5g!D;|2i3_R%aID(vw@AsEVJrUjYE! z25@ikp2cMpVdyAaU?CW(7jY}Tvvnhprx=WQ#d>o}HlWjFIUc2AxwnXHOpQEB%j0~& zq4c;3wd`aDHn}NOq`A#9M(e?u@2Uw|-j7=Z!aW{u7Y;Z_Rni zN)~5X^=7U&Nq906ZEOQSV_l*y(6_UXYh&E%A!{$FKM_v^6T*7%`KW14@P>nqFwi{^};@n}3R@k8hA zv!HjHH{=L&v{{lp;B#8OU|*?|;odY7I(z^|mm{4ZsC0E;`AFfus-ge6=#$h-HS|(>JdetFoBIJkUdg3MIBv?jY$=(vn6mcLjN|%b zPgNwY?Aix|jZoLCV{D&N0Yb2@SK2;VYhD{3qq5|Oou0HNt5gsvL&h64LMwk-YRBf< zvEl~Xy*qSbCEiU>_CCQIl=9~){;4OLEd%d!ZN(Uf3Kzlru)R~ z3qw0XL$=wQEJkcB%+sp2Iiq;VM~Thl%g`T~^GTPtI$~VX>M{Y?7Ij@$9pJ3&21X60 z*bVNk;#^sJ?n(s5m1RQs?DJOZDW(k9WD`GFLO)y{$P@}KHILMt<2ah#UGDc*AkD~4 z&a93deKNhLCNm<{^k3&pO4QOjMB=PFY&{lrcd^T9U5Xg_!B9fDbhGDwtkj1+3F~r2 z`mv@+C5PSm^W+(b(P(9Z@?0e9d$rY!;zEsO+dgLAhIGNvZVIoX_DDza2WytJGvdxA z$zg?&B-*@|+pkT`(` z_2j2FB)O!8U&*LIvTg{)_3e_)jG@~c;N>F2>|;<$X6N4K_%N%p@o3Lm7Nc$WeV~I` zs8Ou1gO-z^J@Ol+R9GEp9#Moc?i|9K$g#_++*(0v#$<95dyw;G`4NU?a!Sn2XT|hm z>Fos_zi=Lxo7jaBP64s9gj584r~tgKbHtiBPthc9yVDg~Frl+oWN)Zs&-)$>-^D>X!Pi8!^F zW1aV{c{C!PJrBAA*tw0j#-Og_9eQ*#k! zw?%?c%y3R?HR6x)%an~eUS>mJ^&MsK%hZ_yMDFRlloqItDA(M{q$szFs6judhl0Rs5&Rpp=@dCd)T zfFOv^=R~X3MTA(Y-}_cK!n0d>LwIRmD3;RH>pry+Hg>nLq&hgvF^V>RrUR^D`?8g;owYJk1m8B#rI);y_kQxD>$vX*g|9;~R~ebhmaJ zI1Z=le09P{A!99m7;l7>?4YZQ!RwOb zD;FPw^ye9Egf%H6At9*IrfcXgZYbu_U!ZHo{-9xFA1*FGo>=_6NP z*f+4;H<1&zV9lHauvnjcP&xon%1;y|F;3ma72n^1-PYGl87EhyWTsj_Cv1$b7me{o>CQYq}nih*nP71tSN5du(3Mj!z6TzVop#f zo|yH+2j(qPcS=5F_XyweoU?AD-#Nd#K&2bLy1CTKODd%swB2{P+3WwXd-Xz|PdDgeeGMZ;Bm zF&7epD?>&DP~u84p8?#Ur(7Qpktm56z_c^PDx)D^BtH4h1U{$Sx)OSxivIQP0_}j7 zJ4DyJ2agj|gf!M*=wM^`CVTAwWxmxGiuv%+g;Ip#sJ_|m3Gw2Er%H4LooS+#!=5O`~iZ3#M+-vQz_RIenOp+#3q(2T* z9rLvbQrF}0JcU_qwwI!=i$AcSj-U|9wOsTlDPE%J3(cZScz?FkU}83ZLh7sn^7YAs z-?o^$FO!c}$QAFvodR)-{bU~MT6T4eF@1~!`jvBW$46n*{Q~&D_Gu8eU$H0V7+u{O z{zCc@gyT58H`;x4`a{(IV0`CraV`{HARtoqf<~zx7SCynd5H7na_t9@mc~|S-vzIR zfe`hBzLn?MI@~E=vqK+hyaX};0VTlQG1x6?j*&z)_z$C6&e zfvP(rpKVpts7&M)(tXX2k@Yiy39Q%mK|e`p0C-^Fn8>}276B#qmt8O^Jru5)Se zX5DSP;B663xCG-7mnX$KFo^57^#jMp5ozB!eO*ipZn(OwM<#wh7fcp3&)CoAX$Fby zwc#n;B3+`P3JR)VE4!m6Gql=jS;GM%7lw@Dm)uUzD$b2MSySDIluHyCu4%9y4G*u% z!W&#UjJF<#W4{hnu3K*w>yp@Nf0gemN<=L!@J$F9ymQ`BuCBovQv5M+*b8vfA zYPL7QTJ$lJy<)&16;uQa{e6c~z56V*O=$+r($dnlwXW52uXiLV`h8P@!o}5w$jfoo z4^3iMidmce!JFPlOCS)vX8%lZB&2vlwpy>p2tYeEiL}FG99`I(Ol<{#zBx-XiIMDh zBhcgXpA^c+EEf))B_C+9+?BQM0?ew4^PHi14C^sJsD)gTMtp~y;k} z6O>QF2S>@7jiC8Uac!mX8CnfP-@!88*nnO;!L)zELgtM&R(+=%Sova;ZC<5pHT%WY zg%np262Mts||gS zX#1?sWs|_Uu}|4Xs-hOdSbEh(qUT+2o*kb>4g%wf>esAQWi3TdtCqCEvpp3zodD#E z#RAQ3wlo)UrN?V_CjDWvBHrwV-m|lH6H8jYuq<@p@yENxdzNw!W%Yh-IM9*X`2^Dp z&Xm1VW;@ynfdGk04y*xU4QOM3@;gT^0A? zE`OYrU50RXOycgJ+C)mE3n1S@@=Qs2cIyPZQbWa$lwVLDi8Fv|3@j=ZL zCEIT|Ulx$I(ekj{U8Ne6^5MEdRW=o`RDJ&U+<2gyP3?6=>9GsMZ{^@Jxh=;h?;iVkFi5?w+`= zro!cc^8#EdTUvapNzAK;(2;CkZ2+KpOoU0!PDfMu@Y3Ur)B5?R@VQ1;@&1|Rb|q!@0tBA-*MYb1OAp*t_a6l0md4ptdzsh#Kp>0MyAajqvG43>G!4*t zSg@l^-3%`7t3Ij(^TD$kLSAiK`%qJ6f^KwO)Ll3Cn#r6Fn z%4MR@MkzdUqn!0@U-d>Q#ni!htQ6d{o_4vUpIX!Ha6S*~T3&ZJ8~r>MYPmIsdJmp* zuxQsTq#KF4m?X!rS<}nvykh%(mZ-D%t97ItdXTvf-Z}>`V6%2jT z&3BV?x14|V${7Xea;mB7uQodu70!4PCJ8UybFQ(70;7&$@f;fvRW_f52pf48XDMNJxDDy+tZe) zwx|<#aqLn2L# znKkh2LsQr}ipDjjL7i|!=ioO5(D*#oiKJ9_z&$j-`&ySYsuBNMX;}XVB3T{|hMj2K z&m@D?pC{zOiF}+E*O;kooMF8mQ+F)Kpyc*LXF?IbhbbgH?}?cxG1(e+{P%AD@K59Gl|+cjk#$=&%Q&z3{h87@c=kpsrCh>998K`++BEqMM1f|J^uY zhwkLHx2gxB^x_)quSY9g1Xp;wMAx9K5yL!B*LYsC2BKY23mjW;I%rs-k(cAU1o zYzmX6>-J^4avkdZ?U+E9+5YTHJ(Koq`dOIj4o#>squ}P2`&A`rOGxwzkwaTr<6+|` zJU9LM)MktOqdL=aeKb)fX|ho?yRQ`#L@x(xw|+z-VRy$#Z&c-$J+NSgly*R^-a<aJBY^4wdwA>xJ~7W#vI1>xmv4VZ<{|i@nMUSJ86*Czr(}_ z$p5$!qZDa$X6{FBfnMeL2Wff2&Yci~O|gW@22=RKn?$svDaXZw6HN~>cwDEF?!`gh zhP$&|nQZ2$Tr8is{Z{@i&YEaLCEY9b!Rgr!MgI#&{LiUtgfjXVKDi~W%xnm-2qQ!v zWEVwuIK5kL?Fms$6fNg8%{SO`eYyAVIwWr6e_8M{jiMp;50gN|k^4N+jgVwELuT zL)foyS$!Ni`Mzexbgw)KBTr=S63#L{u!FdBUCgT1ot}O8u3* zLY)2Zx4IX@p`)?ftOy;hB8-~lVT*&|UiJ~rPxNhl`Hw{@#4_9NrLV?kN?MSvBS`9K#A2M&oOLmX#cvnPU z7f^~mYcDl!yAojDehkGCq&RwQ-QbjGolg`VQXCo-Dgnet~@!` z&lQRpg(={0_XEm)4RY~FsU~grTjzEVq9rwjr`XYH`+`0TOFNHd~s;k%QBsvWIZ@V>EnFE>90J=xF#D zM>5yJ9o^W1I_fQ`|7Jj_QQh;R$8M9KX-7umXU3&U0yYtT`jp%;ebqXN)lAE3C8}8M z1zB*@Ef(}lQO-S8$1wy*v*6U->@k9RH^gZgAz>jvNvbhS|tDA zAqWQ({N@oUr7$@N^MoBnpd#IMX~0j`Hy!gCE2BDF55cw^C?e;-)6oJ?>1f&LcYges zNG!&(ar@JSSl#lkn}B;tpJV1MDcM9avLKOtet^&)Oxt}MGxMLK|D6>l7Xn}nKi27L z5TTpk)@Vp+PsZRJhJN_|nGtM7Y*$*DQT%=)BKTAJX`-0C0RFa*_}M6%^2bH*rOvfI z@u>ew?5|nFZS-2xdlmGh1h9zz@jx$)`*!-6=$bPFBn8p9AFUI7k zN4}i0dAMJZ%NEBPSOn28H{NwG4Tl&<0v$kGyp7;Q=TW0@Z(h#%wO=6e+r%~8?pJ~B8w`5qCrus zenOXdA?BT$o7|FCqtffBf01UrBvkJfU|IEvDV;H35+3z0LAW25MqXIm?EDD%=g@!P zLDooM#zX-A7W;;BtDNS(aH1gt*+{v6QzE}CVs|)E*Y>YUfVG?zPWjfqfRdoepml}NjzRE_e|_3|rN z#@fRFdXFjvW@+@*?s%qO%>l(B&fhqfzfI6v#*g2}iCK#`3Rt03J7?T6_5ju2=JlUh z3%tgR_Of5!&jO`BFF%6#`acnFHF_Mpxw*ZCKLsPuyKePIP<MG!(WYG=W<$hAH;j?qSS%&c=3-=D1{eU0>~MynP=tnmVRk$z+QfbT=_= zDyU^V+s@NJ&x*xFof>^ZU&1tUS9SjMa@FijB)fC^6eS@LP)bxV(doS zZElyDVXy0wda+?HYaoG-w=Yd*bt3_uX*nOB}b| zH)~)z3$}CC54sjQ*(`F$B>auA`w#SMjS4y(Lp6{7P*w1DdZf7cI4m{2X##kxvAy_6 z1bHV9-CVtp&wa&(sLw|M##F;(`x~IMD0T1of{Wrg*Mt6S;kL$?`HH(7>Z+Z&h0RHhT5fr)vg+9;w2GX1nr61a^ zu0?LLG!t zoua-DbkzSq2%&%L;lvFFQ596^7XY@Xt{1ZQhp~)CESaJCwo*SDHDjt=@)5+KiR=JR z5_{1J|8eF5NLgamp8DI14{i-mD+O(#X9y%M*4;*`dya>wvlxrZWz~a-u0&99WG~X# z?M3Nmxh#igcn7jrQ@0udEevB-UtuR~nO*T0{G(Fq*}ENqjor zB!*_p@o=$mshwoiG231Wl6!r+(V+SLXT8hAoBf45mEUmMzcFSYt?!^OX7|wdN6~>jxl%`tI z&}_a~Ij2ymcAEf@Ov{}~sxg+yDVoV-vs@Vi_`DEj9$*Y;3b+AkAGMqgPs_Q%)toEF z=qe)ulkrlDLq5B1H?z%H^Fq{@zSGN8t!6bOfIA6BZ?}%V**(RM*?PFUVsV*JRC?2l z@O4xte{kMx-3Ej4dvk+LA$P+2uh{?~d;;K%Q&O)#pEZZX(7+!0=D~ddrMjOh-TmC> zw&E>M7g`e;3qzo9ZgHPt#b3W4uhq-y9S7++GUX{DUGrH95hxve$bgrMEn2oJ@JDD5 zj$>4Upz~cSqOPBMf!D~r98%hxB=9Jnz{~%Tb|;lLrrb)0sK!!_k3}^Qt5b_!-u2b- z#*l1p{2{v*>{i~m*cA7&qS;U~O&X`dpwDyA{(ia1F?vEExTU4hnvNnag-TCQIT@oU z`zeJ+gEOSiy3}BFSIT3u3IMkh8@mIa%MIf*Q+cK?v6%GpOQtcD$g^55HH!cd@XU_{ z9}mss+tA&YE8#`9+*6t!g@a>D(lD+C<-*L=loGBnW+0P>KjngRLyZ=HE}!4w zM@2$T#wkoD3urrPS^(fgwb?YMElKfkJXe}%SlUNp`Mm7L4@Hxe)21=s{7#^MVMFJQ|asX`8B>K+v$*I1l{phVJ^oLRv4Kxe8$DB6x-cal$ z`PVYJ6W*lF^tffJA^pwIATp(J*2q3BAalBSXsdF~B z7IPnbZn@5j0pHqL?mh?;zIkn1N2aS4lzpnjpiFy0p^DZ0@|u*h*0F(ZC?t$(n-gQg zcH^f zqOKC=iJnlsqKP;AOT{b2YM?mt;hPdM?%`q6mF(H~ivq>d&(#)_xKDX~0Kb;t=C(p5 z<=$gsV6+jSN7J_LDFqslhlO!GUR4PK2jejsk3xKD=G%E&c8k7-?xCcuvJzq z)b9%tK{5Q40}ufgvO~#QF$Z{gKkJ{Wpr8_)p)(m<2RgTnI1{URvvr4@W+5uyhicSL zwrM!#dm}h;FEu@S@uBTXt=3vd0K|r9vP(dU@WuP(OVQ%=u0{)L{YSQV$vCRq1%z#4 zZG>&9xNJIt_hW}#j?>N*qd;0oafViAI#O+` zC)D*wjYibjFHJv`J}b7Z;WY%SRfXu6Hl-e0(9tr@I?s>;XnypL=dl)!5~r`e#dHE( z33`-_J(8SDb$IvsHhaCih?;X}u~cO*Cae{{=DW9AmLTXf4I{sOtz!}8+PJ`Uk5I%% zck^S4S|LAYl!(-&9U4qFU-d05Cr!YXDxUAx44CjiFjTiTi;SUqXq^)f2to`@=lAsw%&6979Yq+N~KL(&sn8qKE zq-rQ*?LX~)K;VYWE9K?okP$vN9 zHdLc-ih&rDUi5r-Yf}_n+OyJvb#(c=2p3Tw998@(OP&XS3sG8*KlT6`pnu2LL$q+_P$tu4rnB9a8z^ zeUYwenxp3WuKqbUBYIPr1~zJ?nO0SZC>n9MHazcC(P}!>K;_B3Gh#Co8OEG%M`Cvn z&g8-o)ZG(|*mn+A?`~n+uiYiyXKmMT59Y-iuK(SZRM}6Lo4Xpr$ zsf#iB+|B*rS1dWg|DGj*BM6pIVPNeG$6pxK!B3*{(Xe*B`r!2$3C~Vgc(h+&1As=a zD<9A?<>=wRRKE($Bvcx`WHZ1Oge>e?WBV?bzNi^^iqzdS{$J;0x)8zhS*z2(7mN@ z<0U4YAZ-t67`&U`&qTW&hdoJW9`Rxx^%ny(MMDceKTAK%UzHCrwx-+g=j2N!oGw!n za^_oWMtEcnO67Jf#h)^|=lB%Q8jJ}yT4X{w^w~h;fBXw?S+XaO0BbJuTJO8vN%hNB zt0#ONhj@xZikNwCz*(({zm1OGoVE*JRb$C(^kc^ly2NxH0CwFjxai(*ud2AVPADlB za>=Mu_48#kx}+6xnF%Cpk)|&m5%;P;f}()ms`ue?hJI0xu_UXH3E|~xg1=PVhmN-<;vSApZ=P6Tk27O}`4x+j4ujkH+(=sb;@S%<9*$wH0G(F)9`Za9@fd z;W}-0-j&)Kw0#Ng0d(Pj#lpDy)NU#`%~~#H(p(Tmq3aqlhVy<8xX6D?B16k>1`7vq zg={q3(s+@yj~N^MZ_x?=+-qH}B;@b5|J)|+d6H1j2i(fejA_el z@MHPirnJp1DEji?hd4dPmKJ^BFko1wX&9Cxkr2e7)3oz37E>x1c&z{vhsJ6&1`@7S z0qH`SjJP7D{ePUy-yd$w??`ZW$&OU34{8n(&%*w=cBW{_SjNw z{pnkh#6v&$dTlgD$;de#Sjr4%2Klz011^2=DnR9dNtFz4 zSK`gnoc`lu|9SEAxzojhf3y2aV{v^}tCgX%|3(-YG~mtVUR y&5~D#OL`5n=D~w|Ft1*^u_Tl4q}k?pCihn%;utLU!eVVyf#x z3dTFX;3c9oh~@faOP=4VdnQ~3wd9xC;v*-WZ(Q5NZ|VQc?*3EIWOP`FDi&vYd!6DM z|Dq*+L?a*!X!^YJKc!6nwXn5}ry8{}6pE4OF#jC(k4RKHQUJJA;xACne?9jk5dY_x z+si_XAuNAY{ryv3Z^#Cl-jM$vx9N{TNF;#(Ge!&31ReRGqW#Cq6M?X1qTjznTY$Tq zsGjQE&O61c{%1PBx6uE;*gy67|Dk5rqR3D74_#e0&VKTMHf>jVw#BKR^yzK&aqZU) ziXR`=ERwJTPtn$Yn;;0wYl7G6%9haf+Q6%~ zOeg2S=W(xsV!I!i=YN~1H$M`;F)8m?A4wp6iylocwZnfV?&Ag54JP>!_zUZAGw!5< zR$VfuK4M6VWW<2sUix%yR3;_T>~s^k(sH*8&ty6NywUN|TgpJ~tIOfxr@Eu1nkD+h zKkP7^Tvu^voQmr7v$x|R7zmRSvJL$CIsFKQ93V$+{cP|urD<=k%JAnGq|<7E(?1(D zs^jO>k}AKxSl7CB00PO{a&Nn|I$y06_AF;aA<~U+3VG+8IJ!YLaujQ{v1!yZ2)7xu z!#oI2k~n$w7CtsUw(|_$HGWDO*}g5}4l!?bKKt(G=9X-65vX9e3!XNW2HC27HylmF zUN{Qa6-c@|UY!IAREocL+?cJ@cI`9EEZ^*3n1U}Z?4{1EAOM`gQA#aMl3e3)QA8{( z4Y4XO3+KvA?OjF9!)96$@;y*2_Y`V?KsqORCp8p z&gxFoMU?%c#ds1) z3!I}SW3nr-bff)W4TUX0o9s*gO4&H&pgB5}VZ{>~n!@hR`V^J40NYQ%z)vl336f&` z-2OhIv3F8YUeuhG29?ut=SVyfUmoUON_Aq&YLQz*^qA9iPc-G}c(nXJjyRI`#O0RF z4`K1kPgikmy@EeJkeJVqsx2RI0LH#J5?+sw0F`nE7WSJnsd`wEV8WPZj}yU};CI>Z zGmrqR<)Uo0s+VZLCZi4lw@&stGI7ZT;ru6EKMOh$g}mfTAe=4w#AkOenlgHkR7d4f zZDe6y`W5Hp>_iEgeMNc1J(uxvCIJq0yiN=zg^S%_GP97^gvXse3%yQD7~n$)gznD( zxcLj&6*>Tj_vAE;XZ6YCyCp=jtcUAWf9WwvyaegkZSUJlM$u3{@Yn+U<2KQ(=JV1* zcUR=~V^;+1iuPu5;{bDazS~|T^uC>4U!gtG-cnDna+KjnbfAfHk=aH3d#=aDsyI)5 ztn2&l^3hash*yNE^z|4j#cDE7?C!bjG0LkNoD@gQ_YBK7i}$O$)>G*YpZ1qp>2Gds zt(%BdlmXPUfFZ_Frg+5C9qJ^ANPdX&o|j54ySx#D3G;MqT}bI-96K-CP-0=K0EVRp zyZ|ST*^h2|<9k2=K5HjuG40&tSO0QXUQu=MJ}A`m|Q|*|BsK3pB&8Y`5WnYfU3<%`r$ij z9so^|_ilDZjJ;t@WJw32kTga2^C-Dt9-%Zb`SLfFCW<_^-_NVvuSg{E`FNd`ZbA@X z!0_qiK(*q6>j_iK*Q+7?2Wn ztr1;xya($45x!bCBoBF5NCN+A(}^*eE|m0lFweF~;S|}(JIi5D#UUDrOcV_zoby&h z&1E&8m}5Y2df+=&kFgFZdAYZ| z9=1r{JH;9QFtIr?ijr~h)G}9EHQV7}eog6*CAVl*KW|JsGc_fiK*k^v{3IVOQmj+d#c-3ODvF6Y%)otbm zJpb4l3&h0LI1E#5DwA!;alps3tM8s^AJTvEv^+`ywkyR*2$oWlGQDZJ1r~KD(s1z| z!5;f;$CAIbACJSb8iWa!c-PKUqfoA;G1+X;`{##ed+2N~>D)}$nB~P3tEj5g=F61< z$l*L^&F4(SM+ira)9=~|ZT)y07b#Ca(nON^GJ?{a6Ez(Vv}_J|V1wz1eLDO@hy4iHMiYtOoD$=z#g_}y|#I#bDxUMjyGGC8As6UI?cVg z;&Lp+C5xb>$D+4(xd!X9(AcGm%4+5}i}(yU?YwanHL`AFcFk;W?}4HY$-4?=4iQN5 zc3k;vs7dMpR=ZpLQL^fr_`x*3qvv2NG0Cz{G-dyr4C<05>zZi8w9~--=_IE2TliQv zvUaHES=6l+kB1s?7oP7gj(Z2)PnW<>j|v{;(_m=WaoHPpM>ueY79ve$Lp;fKUUEv; zO+bt7;=Ysbu$|NnS=Xsu6hfu>jtlL0A?Fz;ao=H!bkBr%KZU}W6zKHI6T6GcV@eI|SZnLAE8|WcH1lcgPoU2ZR1RCnFIR7#_fOw_e z4IVX}m!}4*=-6buoG)^^r3BhbD|N#D-Vsc2v2 z>2s*QAmjvn06L$A5idw3DbpJ)by(5OT@qu{Div{Z5cWnqky zzHHbJM@Z*c>IZhMAR**3}LT3kMGj1Aj`Br(a& zn6nyb5C{?ba^cV_SEuB)IIfNmo{IQwUE|>=8j-HLJ?c+H-m{z4pYtAlF&#eNbB|AG zpktaSQZ9K#+6;-*)a8sAZ8=Xo=eyHU{`pBohy4E`?X9Dt?!G=yk!}&BOG)WQ8bm=v zN>C(*knWD58&OKSySuw%XoeoThOVJI?)dn7-}^p4UH6~6)~s2u&dhhtXZJq)oU_4V z=V=Q~1xBz;e3VE-AMbLePfWXzFQUyBI03HY2ge1PzD?Jl-bN7dj>THgrgoK-aIlco zE1jigWE6_WhSshJ)kZ(Fxu;*@B-`EUPT_;@{8}-ZP!8QXSS*qd_OEv(kbMzv$j@hUArX&V(t8us2sWTh{RA)5Xmm-$|_7sQEo0 zJme0Sx_p-4Axb&oL6DszOvJI;x8s{?dsnLNIffUc59S$M)9RieIu2en#w`i84(!pM ze!_cShaR~W32uFV+n$M-9!7HNr8?c#lUjk;O{{Mh>(;J}g(7-T4ZSAU6_$wU{lKjk zrsnFlN}fVg=j2M|5`Ol?jz6objlctOCHeCl*6?e-rT~sO^@YJHB5B4$M8?phIMjcd zpNbFTiwB}t?rkO2r3b~6G~MLvB7rCjA?3s8)+e2TUNcXzIc%FL?BFN&T(5z=&`kDMl58RLGi7F1$Ulp#Wc(- z{ynD!CP*STltRB2(M63N*V(6y(fM##0~sZAk(tQ~LMhZIM1{pg0k#Bj6PJzbK z-z0XLvl<>$-I=7=xMiHUev8k4+vCz^;dFh#hjXzK8{C7y_uBW!13DQ?)Tf@xocy7u zvB5@6`qhYiRV4|>J7ke%!rL%>g>tf|Y=hdg>lB&TyR(rFJG#Vml%&p}^b{zXy6TL} zXL0Fyu=O*L7rdL27(wrRGfY{8#=5kMCqNYID@*n3HUSo0)1?}nCA6x*S209iR%GsV z9h>3GlO@};|FrxNB?4nui;-jOXfB|c?8yjQlLUAL_$|%iu`ORb9RgZzveVDC^+|3m z(}!^S5YlebCP@#QD%3UK!_j5%-e&av+zeRdAp;}^S1&h6Qzly|7h*c`qjqYsYaC1C z#iF*il$#vNVP%68xdbYJRr29l^F?wh!*E9J1Tq)~QdN5dj(=+jT`~Fz!#|C+8LN?W zs`QBTiw3#|PYyu6j)yj<4Jg z;N>7JryN;m_P9uPoj#j%X2qK7pwY>ikG4_(o#G;5UlV<%75r(P%x)wI4|v*83_{5G zs!)ih4m%&Yx-A20XuemE*dW<=l*Mb#2MF2w=61m{rRL7I1rIB}zHMjQ-D`t2e|s@1v`zOM+y$#W}q~6i#F)2gQ(`BDD8|MyS4#?-Dj(7bY3+N2h^h+syp6eO^Yd~Nwtm9 zbn2#aj5)}Ql&=&@f_dmz&CFC)F316%=Z#w5MaL$@8%0hvC6w$^imd1Aw${xU*ast^ z?cW;F_C`7%G+hNj@YwjIn6YSz_`8c}g{^g}o~PY;;bL9JLm2_T9;ZwuxpB8CC2SX;QVZtB^G+~P zLILae&Y%f8&oSVPVmea0khbu(45{6{(~A`%+fY*_udTUG2P`1j7SWgI%`9~xtUgD$ad`(2DYIZ8?$o*;E0F*?odIZqH z6K)~*Z7;IynE2*4Lrp_%V>0AQ#k$a$E1Vq3&`10ZbpyKNnXeEHPt`58w?ZFwD#J96 zw!%@7HBEJ;&Z$}-EUzF2kZEIU0H|TG22^~T7pYJwg(B!|>V@3*-GvfFaKDNJ54u#%bFewLsJ&d$nZ1lc2SD-dgwoeiJml_z0b31 zdNM3@QuT%)*}Dhb{T7;i0Bj}q6r3#-fBauVt&t#`&2(zGZ^!skvVKM2$ zK)JyJVq9cmUbH38^QAW*i$XxlXj9V-x)TbL`tc+Lh+^1co@YUvhcPp*G638CqSE-Q zx8n0oS{{of^-_)4>i22K?>iaJtJ{{zXVnnLWA1^gM~=;cf-BQ&$H%)?F^izH+vPh~ zb#|1&7pV@WV%n4JcMySd;8}T)?;~7lthBadGNI|z#Ru9Y`|YGImX|2|98iplBL=Fv z9vlv=y1lRU%LND(10I312u+y70^`Fk8i93TQ2@ss%MjisM;ru10-Hc{5#};2Vi5NZ z&VX*20F`Fz5mpb12}Hi7)0rm8{0CH28g3uM9}T=57AU}i;IhGsV=hVkuhW}uIbFZ z;?Z(m@ncUaVESfti^k=dO(W#e#?7!$q$^$H3(1zMPTM=gxK)ztgFxTCfK>P<`+#~_ z;Pe@+R@2p>!we?Poi+vCKnqRr=|ZGMuN}jB-OP z==0Jmh7<%G%=<>>%M0z+4!aJA2z)OMfgF)ABK~)dHpxAMYC{RU_{nU8InLN+t7CMb zZX=uv`VHt>Q`TqQAB-!m-&{O46L0qA)oL#D64XU z=;BysD5itb6ES4Fs`i-XX^JqKap`)nH-S?RX+!!#0cKSpzrDDT7OL93nMaTF+@n)j zM}@0w=50G?DPlB7>#j*;un9-ohOfl)J?;*;Z-BM_ynaR6I7z8$BoS8s`g#h{&Ywhw?SYPx8s1jb=W zc33fRgm^ZwB7+35U?;<`x-Bcv)QE}SdP>I9IBZXh9vG#`iiT5?;W+Nkzx>H7JaSm# zg7sB*WT(Rp{6TYI3L)ge{Aaf2@5tTn=m6kEh=Og{arC|cJUBQH;0G8PIdx>|kw2yO zF8=XIaCZ9YN)Kw|OD8lFm%P~Ut(=Eeu>wgaSg&$se>U(0XS_}~4})~b$J zXI533>PyBn&qeai3#p$TVenw}S#UGX?9yKONVWNEoqQSUKs0km34qo@cTHLj|0N;= zSCc{0KvdFzhV#!l$ZhCz_IR^q?Sv1*HX9=NE+1rn^;-YeOR)fbYfwl_FJZtN`w&);q5HiMhl&u;OTG9f-Ol!?}Nd0PU?8$d&O!@SA@l{)Yf`((tjqa--pY5#u*)o_v24a-kSU9Q5A+ z;CSAL!p9=?z)62WNKJr13QOnUeEJs*QRMJ3x}Fq)$iIMRe9W^bsr!}oFBljF;A66( zB6jru#`cQ}-ik=ZO@SR<@BV^8(H1@yOYXYy;V54@Pzg+G(dynl=kY*`x_0O{-Obr!-mXXXn+UD8TROh(qH(PM*E8f=sSav zf1$yPCvYJ~Ak6$7jr<#f417%M=|nukU%3*7!1Bc`J z6%4`giBBd?8gV{0tMlHt-*(AFv^tlcPV1uPf6TUhfrI-O>7ph=kGh%j(^R>=mre~& z4Gas9VO9SE&kPUUOlIqTY6>n(sg-eP?@SicV4e{56@o>dKVw$u?SAp`|LV6lIjv%e zra<2)nD;LR;E=6b{l_-FIQG^>&Zx6x3{l5|MSbE;M=FaX%edOR6! zwLXG)q0qI0@E889k$NZbE^-!ni~}xYP7LMN4<&J9^g{PoPY7AU9WB%ucG4zmSoLRv z_SRwcNeW%A0&3^!+2rq1F^Cbl2^yi^r0ZRAlR#3 zFqr)~S^Hn)p|*UyX9tptI(|pZkG{6P-ksw-ObVOhvH1F2$FbRS3A#4U-U?sb-8Y)5 zXP0kNGsZe&OBSSWfl|vmzePI1{1+f|`ZiI9#K&-}I*(*~{>f%K3P-mS6mkq5S*e&C z7LvKCvC!|Y+U9avbW?*D0^H7qoW_b}bL--jDF5}G-@;A6?46Vh6uZe7&_CPZt#Dir zoUy9~to3RIP6O7hD6FGT1-4c4KNlB}TTHb#nl1l`X5ps!i;%~`nN+x{c>j!9_5EbQ zr&{M{d+1h#@&_Ef6k;{5)R;C;u-Is7KbtOmth?JZdCr~Ol$+;0Os?V zP5(4Fic;usyO{Y5{Pj;KwuS=-P_L{y5%mYT(t@AD&7u@@O81{Wtd{}~z%I8W@8zH5 z`v1eYfM=<#Zl404#wGnR9WSH^d^jJH6vhfW=?9-uae=0T_co? zxIZy@Aw7nF0{0F6eaGrEvkqy)4}b6t;pw+`a1OlDAx*B_fA5p}Cx>R};f$U0dzt!| zIdaj$$0WVpwHROR5N9bO{Xw2b=$CL92VlB&HK$}Dz?SmrSEN7Y;Ee^BR0VuWqq%XU z9{oRO@DmOqEO<~1!D#8O{*dL59}(Yx3yRTj_|tzGtKWegbs>B#H%QfJ_Qdp4?bCVQ zLzD0N6)bphJVr_}w90=xi~tudAtbgX+J)+Hz6d8G1<1wG;TvsYVqcnn%=s_5_cs5< zqvtCwyoKuM+m<@0nIDagxK;f7+|j~|aS2lXD-`Vg6(x=5aq*(9aFtq~xUhYHmDOH! zsBK3mEd#21j$Y>waj^Wpw}?38ze!#3o_aIn;%pZ(a3O{rEtkkgCmxdeO2&hnPjjjS z5c^6s0aufzl4nGC!>R0=vIgK23>*XuR7P2Y^Y8G(r>IzOl=q}~8`+ox|?>)R;HK_aOD&6s=9ee+k zQIhFA6r%q6f4rV?^=ys*heS^9BM}W$)2>1#Dn6=`P8?Y#7h&Fc<$c*F)x#NP9R&67 z#8Dlb!(!a_)7efv*nk#!|HsEhk;B7=SchwYPLX?fsV8!P{gpP8YDvq?5Ll&MxN;bP-g!4tlGJJhS)kepPzwt zXDsQ)V4lEap{&L(I?y26tA=VGVsVmD-CWVna$svlA$}00cJjvA3KSA|Y#a6HAcU@P zN@igvn&yFT!}zD&=lxgLNdg+qO!ALu!>Pk#V`EbFKU;>& ztuxCKTrr_yyto^tfEG@_@MlYB7u1LuuJCEag2F3h>O=TRG*QsfTQ$DBP>LG7!IDy6 z4YzF591o0`FL{i$I6G7MmE8^@Jzy@Wv}30HdYx!i`zY6jV5NRj8=_K;C}$M&5O7aX zN9Lg-(jl3DvyvlkD}Ft};Z2OyHP-DA(_J-aDQrwzdLkUjHbh*$VaN*7k}NEGXbI2X z*>6y{q~oFTa}MDz$8!eefhYU&C)(~O$@aTr`O`aZLMF>dq(Z1ZTbcv~%HvHH8L1E3 zYtD6UY=bg?*qfLLO!y+bX}X}fK-Mk%miWjc^N_5q<{7_Q|L;Jdm(V*Y31~GT^>B!}8WY zg0{D^O@1=m`viOPQ-+?o3Ie%?req)Ozc9U7a4{k9>}yN8Fg&tbBDGb?YyymDP^#8z z^4))4x7}BA`eimoVT)hGNX`MP6F-}Aerg4hGbRo)ug;$x2BE2mzRS%F!-j>k zQPA)Yboae;YoM^S5)?v&@VI~4nPmA=NiHQXcoy4Y_(4q_9SQqVzo675<8)_KR9Q*G z-NhZJ%0eA1N_8alt+)?uZikuUErL>O9k@a*_nPph`Nq$TxN>brZEw;>YmDDI%6Nuk zJ7yvJZ1VdvCjB962u`*W{qT)Qbf|>w$86}?1X7ique_MgR0x+Ux@ns3VU~NP&mGEX z5@>}1y=#WP3VVlP`x;+Sw9e@am(ziMQtTOc<0%4`e!fkzMRT#%Uw9wj8yjyTFWq|t_x>622Xs!;A&9LPb|pH+5W`7Ub=ZD^LU1OxklCk zI@4=p7L^Z%6qlPDG=Dg7|H(?2e^9$R@;tOc0<1a88+!Z^hr9a*Y~S|>P>0XFY+%wBk@?VE$mC0pR9^{0U$tPZYh zCGoTR4M*x5Nb`Kl#k+&qMDMImzRA+B|Bu_2fqM+P`JMQSH~4G?}Ldo7;y)>UCYO<(Bb7|yXN0=5AJKPieHJW5&YoT1ctmrYi> zNr_;g(8mMzH)>@=A2-m_?T;=RY?AsXhzxgy1Bs0goSPNOQ4*9QRgHg>|6+f>F4-ds z(oV>mc6&T>d#WDgM&h(f_e2$SCR%xRHg1tWuiKoY$C7fpim*9wT&A%)M*?6_*z)~H zysI{WYD#@Vm?*z}1-0scMDuTUj4{B2fEPmh#?GF?nWP?C0()_UDt?agRq9bcNEcj0 zx5g7i8B><7@_<);jKg+HLI-h2A_^7*rq(@CNof~)PO4f}qn2#Zkj2>4tm-stzBO0skIqz4n*W=&Qu>F-T zCgtVKt8$t4Fq)zw2NaR!yh~(i9)4WVo?_GApM*8iE!fge6gAx{g4}(#(kv}TwYd? zN%J#QUU%JyJH-}#XD{8{w6}oLBRP>;z=hJ}>=l{A6C%%EZ{6j7yRc{p`cy{Gc`okJ z+^lg>_v`hj8U&i=TI;2{$~hZSYE$PeZZ5TCrd)lSQ7PyId%S2}4x&c>>Po{-7}R_x z@54_8Me%#VJQ9Ljo+PLE=qOPaD*Xhp{)musHBw@O{($Y@MC1C0canDqXS-iabLWLt zspv7qfC=&^6EG#wY8t$pOrp1z1$A1X>ytCWQJ>D_a+`qzh!oN2B_<-y&?DCkB>Qm2J&~Wl(DtWQi=#1B zBHMe8)&<6@Jh)k7M*0l~JY(u@sHMuTf((U>-E{@J#it%b!HLdFh2h#n^c!c z`Gl!N!74suBax-qF?7Nhq9)lY4A0BI7&n>QJATp@ySp8hP}oYB5(<8X6h&n4NwSx{ z_(HRStI?+VHlfzFn1?EmRa+)n;tWwc)<$MK^J?d$oq}cjhLH{W8sy3joj7-oc zJ(_|PkFz|7C!ao7T&kz`t=35iqL|x{pr8=kR~4DoF+@fW^IDiOwhC3|U!n9(c>Ajy z`Hc10XaN$Sb<#ekeV?x?=pd}9$KtNX$L+wOC0F)3i$`v}Da`zPFy^YUuzs>-PiHh9=v#1koW?g>hndxk zO}_!Du!U2XB8YIGd#kA?uZla3!HRxCgQQxMl;DI{3rEIbty!SOUc@7(wAUetD#y)l zKRPa+T;jx+=tg~y#$oZ}JscVJe;&^w-dc7{EjM0iL8hA9%JP>XHs?i}LD?=ez7g?&8YcB)*8oGs_DIH@!c!6YXL0nFqd@Ulk6 z;9YIk+aKe+&vYn&+#MeZ6#sJ}*&3WWl{Wm1o&G+(?X ztM>F|pFUk<&Q)DWN8hT^O~Mp!f3cL{)B%(j1>^oUZcBhwf!?J6D>@&z>J~nlW5Tn< zfl^!3P8}caAIhX}7WirX#@)r?9X&)Y%sw9l?};|hZ?roaCNc%zPIb_nG6Z9zsUN?pQcNvf)S(X>>4>}NTH(T{$tyq&ZDR@2=PH5%XM zJGWmmzY0T_)4(pJS<(lwY6}BN$DD@foKs6OtU{u=)fPt9efFZo;QyGwBskhNx9^&h zR1*7cg!ROoM71=&R9bG*sDcafX^!7C>pAmC)clS=dMF`ZW<%sF%m}g~3T@#VWN#X# zd{Z?w$9EZjOhs$Ynn;AAc_wZHHDWZJ@29YthwQl_-fa&b3%y%FcL|EP4T&5Q@gZ`9xB zcapUVU(^>7jR}&sRz!Vn`gs;%g35gV!`o;mC`^`L)ljo%cBa=cSN?1gC()xPw*qHw z1p!PQE2C;TQXWInaJh{iB@Z@BFrQ%7FH}0<4GvG;2y#B4Rwky@y)1BJExZ_?lz>HZ zTzpF$W>#)BTbwcdvFx9AW!z%pX?pXRs#um|>_m4UP!NYqbs!CBOg4FPy$wN$l2#;@ z+x3u|9FQa1m@bU;xR5P`qxim-LlpKWmX*UYZ|fSb@-#&@H{uU$rscii%*{U@za&*?ZQSOyTFaF>RIO1F0i?_V=eQH%+RYVdecHRA$MmPB)9nr z(A=?>E$E|B;5rn7IioeDdyI)MJ=Yvt$X~ozNvd#m0le2TXT+}A4S^7h3B#`Jk=7{A`ALY!u%l@U(QZb zAcD9J2O(5vjr|UDu{Y#4;9O=QQ^78X?M?Qi1ih+4tMj6r`o*FI4@VpKdo}0r?km7t zbd7ZHR0W~*eP4fui}RK9xG~8SZJ4`NlICSeQ}ujzdgVsEyjHxhcGHyv?(H+9nP$Pm z-_~3F{MRxf<(zomwPbOCH-Cn9t_*m)=o`OW0W3kaVf#51#be@aCP^XGSv9mB;)Wxi zQ+uHhQf=st(^{mAqChy_27AN^_C)Ypv7%Alpco^N7|} z6pSt2!6P8r1k5bfq@Piq1}o4*T;!mCUwIL!La`X-l=diPhQl?L7}vdRVTJ=s=tfdL`bnT$pNvuMi)3R7t*g2 zQlLq3Y~cx6%H2tKskf=}jVkI~$$^Bb#JMfFy@!^Yx#eW04$vi^&X%j_^(zFBO4S8i zj|-lTi^Pj246}zZZfa8Sh{=^&Xbxm=Hm8z;T^DZv>$zhT5F72CqF82Wha;}3bFvK& z$8~>!&+h51G0#6VsMu7o=DBtCl-~dhw(85SpaKNEAshY*#mdpp^&R(vdK0MNc?|D? z;FMAJqU&r8~ z0~j|`cT)g;sDDisPln6y+d5=z?5VZE>%y{|QJ`sE~WC9E~g%QjEzI-bQN zJW1sWDf>hSHhet|&CvjT1k^S?T_TCfe{~$Bp!fd2)h`2FalKOS+1?z+`{v>sOCY`%Be0Zm@y9sLzWM zQ)31+2${2@?#3%5R7HjZzRy_N&5Xoz{g%rAT?ozirK%CJOCKD2v)*Rp8rU~3SGxWFdR!wkVk+?J zzv>C*&F~B_H&27O9!-n?jG*uPS;g-AQcxpljFzOPk;y*R<%XJ-RgyU)jCUkG5M@%W zHSo%QJTQog4#$2{B7mB&pa1*3dH19@wh%EC+K^i8_A>V03%_ZXXx6n?v_<88v5hUl ziDs>lc|VVz25a+jaZaZNWT)66=cVV`DCVqU91@R+Z54Uy9$&<~9l)cvkmiew+W@E+;jK!Y|f{=l?2Rr;+OD=cgkh5;gkea!c+#gOS=V zn#H?8;NADeEOXxT)`7({0)6!+*GOc|ipbBu%+k#_^JledMm5}t2$MXOv|u6-oA zwG##j1$QnCQ@)HeEIDVz5zM1v*G~J!OCUcU%7mOEnl^c7J`*lxl{^nS09sIH|%Ox7)W2PxGY`yrnae(LG zvB#lB%k5HIYnE>F`;YY>1%S&#GhBKeh9K8C7pH-vmeKU{cCmrnVRs>yV?0@b8utSzQN5=gS)1)PteT3JU1?JT=FvLh^82-X4tt}+KL&c|} zPI2>N&#B#*wL3j-2JEnjx^IFbnNum}pHJef$lFw8({0$pf@K^tSV zfnCp)&jMI3Ug69r85mzx^l^Nb@0UbPYBKTtl}p~lAo&;$)dttE$2agY_s2mRWCIEP z2j0{KSD&S{v#i%&+|%(Es-i6@d2F7C+K#njvqll24#l>M(NVn%>L5l$kGpDp@cC>z zdL)+}vE3Exzswbq*~CFUsker&EM`PL`ej(GA!x5vIr&wWY@7=uSmzdp!_!#L@=JrQ zFL}K}${<-)VX&Kt^cA!;`uO|-1I53EFXb`JU2k#r_%p8BD`$Qa#oP$tTgOAMr)#h8 z@mwb(XQ&QdoQN%ca)f=9wRE=$7~)dXV#i8gzz<%55pi%JEBMRG8`CSz9^o2}!0sWK z9-DV-4VxWPA}AklTfYV;@*G|kc+B;-VQL_2Ik9V=_Y%Z&37zTSU~Sj74#$!yEPWo{ z9)B}yp*;C4a;m)c1M$1IM}wFf^*)Eg;cnZmozC91&k%n_ENd9>vK9hwHg}-}inVw} zRPN-~kMTV0ejY|#b4Aol?*6nlij>_zt=-qyljiHiSH@)T_^Ei~DvQ}qf5>jy4zwVJ_yHchn*5mrr#zJ;ne^kHC$ApzD0;Vt+us>gjTq|J6=@-fw zFMOV-8GbYvXjh!FY_r{|yaqX9e(FXoEg)uS2;$^+*mFQ`_=yLG@5TMS!OMD$Y&aZQ(f*`^v~N=Pfk zWX2PJq}`DOO1ux91QXKE_U%8U=%V6C1#3Ki;HkVu%VUnhdrrb`mDsVjdjtuq#2R6% zPAR`#2^PZKL386^z8N-zA*>-ZbBzBuUKPaR^P>iyXMkJvEl9b0)Wv#x&MXykII_xE z-Xa>NwZAqB_x)mg`!n+M>~X3qj5c4pF+=m{i^QC(^@)3sZ36;#$>zdEEGzfU6<&`x z&KSRWvzchJ`sYYk${tg3g374!xm{^V^?H&gPmJlOe%a23=rwV8H^iETul}|mol-{N zMrj2}n~8oC$_6QWn0~s6mVv-H*2lJQrRw!fB;kcjhfl=c#i;I+KFfv}=&iYRw!4Cb zO`Vgb8h=(9Yj)S*U8v!yKw9G*=V3AEQ$lDK6yRQciCs#YkqwxOK~9qW_+?im2KW(+ zvfza9<`ZiCeMU7eqM^=*m&TnETtDxVTQ%*P@QC!dEl%rD&Oz`x*fzO|ilQT}Qm7xM8(VHK?fK_)d+NzjlG!#B)RJ&v2m08PxO}DT z3iJc(GuaLf+oR<<=Cu~>gI7IbgrxE82{?Nx05i2tN+6Y>;RI{YsB#2EU$sUwkATz$R!N1Z&8-yR)UqIWVu=BvaOjZ@R-RlsUES7!eeV>MpBEsRtwNP{> zr6J_9bZt8-sr+=kWrBE!BZ9KADFmybHj=zb7SMKzJ-@eqcdjC@r>|tqM zX4-cZZydQR*U?2gW9IV&#x+t}9XR%E< z+6<(q^-Kx&sBScH8|n7MKv8TXU#SgZHXzCcH15C3D>?ema9EcUBQ|?PBF!`V8uh|& zN@RGYrDXssV1J|~!0qCVsw+E|n_dmj@KdiPSG=EXRBFO-;c9rs>71}f^20leSz%EM)Y1}3UT)-GKZ!Mc z6^)L_(G$1XZ-uWq?H#woP{gxHa5f|&2VE%Dat?)WqO^Z{=ot|s573*$01;}H=QQ@Y z`V(zp)$_wXv=nlplXP=J6N5^W#&?$eEy;YL5AFx>+Webm)r5?|-;N2%wOGzb!I;Faw6x~=$bKf|HY~OaiA~p_|vhV02 z_8rBJi|z{0vKH_k^*m6LE3(-E*@Uf(g@m4$aSjy!{6X@@{+eM(lg5cO*^$o|qc&-> zL+(X>zdu4iX2`I$Rlt#hU%Z2$o+${JC7gS_}S&cXF?b;{9kyg8?bKe@b^wG%s+h~jqwR|4PUW_J&NCCYJoLtvoy}{Dad4%HoK{dezRqwD%N0As9zKtzmK??fLt1l(xPVm_FXuctq zGt%HrryRvaH-g9x+5ue^S?%OP1vgtx%{a0gkZJq8OX(ZHd zZmdT0Dir!Na?4^Kc(}d1t7nEj0G;-m*1aP5+26G_W3qeUu^&*klKPfc9+7vjaL;dR zSb4NO8_Hi!)CNd?cDJ)?TSm64o0CT8P*Gy23j=e5shyiu7_FLccT_9BQdjnEK+A0J z^fMMR-KlxZ6lZq6yWKvX9p|IhM(TPy=YSYvOzR5zDh}lsudxY(8Kjsw^Fe{Ts`LOv zPuN3s1to5ryU{<}i#1<7+0Um+*EgFgiN@aLe+NHypub-5iA!&T#bmr7TcsrF8kOrc zpNaIHnY<(AwCPCxQA~{w2`!5Wln-%Ince}F;iD9rB%XbcABFv_Ef?eFJI&h>{keEM zh{!!BFq=iQL866JNyjTwG45`nEOR^9r8ATQ1FY+4IGNx>vZGSbWP5m5cSkx62;Ync z#Ei!KDSeY?o40T!_WghujB9VmK-!5@*-T1knUcS=m*hJoqTl zchGiR!t^FiBG!>DFb*cv>4`h@7>J*69O&llPnU#KjnPL!cyKvyB^47v!G{Z=%CC8x zgN@b2mLc{q`2`Nkd*BxuJ#qF3ieOPIk9x)Xi>-6MOlm@o1W}GfB-|DOSx4bBr*;;h zbK1OhHNAsL$My)Hl7!GR2zdiO5W`TGY3M^8W*B*H|>zoX7 zt>t;|##$)?Zo08uEo0@87g4`yL*{$X5_L?Bp7`j0ZZnq0dOpKV=&O6U;oM)AY=OdW zqwvCcuBE_c%hDm+=z_vL+5KAFXm>Ija{15p>w0l&GtiB;a!&v8IJB$#D({`@~0A?fsy2rW@NtEibHaJ>b>sjW%d9_z`|}OJef7# zIyd0QMYQ4FNu+8`^-R@5P6DTX;lNPhf>1N?#zHiJgs(OHeT+7#Jx00AGtJGVTU+mV z33lts?m_s-*dvHK@nRf>9U_(v67lYAI~LbG9xiD4#>p`%b4Xp$UY^qq9g4u6+lv@L z{P=h*yg>7lurSjlw0yP6W=xE_YfxFSXL%EKO=M`G84i2kJD z10%9GK}ZZn&U%B;HqvD7`awg^Z?}?n7OHeZGY@^d16-QHMd{La?ID`{;<7PBhAZW7 z5GqT)K*G+BoG%<3j(v_h#3jk}h&$w6`Ua1`eul8?)T3+8Spjw7+xH-x@f<7-WVk_| z+DnTiv7c$?*>RJ6nR(_R;Ml)HdX~!WNFpiN!N+6cHKeqFQa1(7xb3?L2 zw-i|?)n!_dbt=fI?!wa~W?ZKp8>Pq^EM{{4^AKfE&1;!8Wr)=18p*kDPH(6W3+(uO+tczT#8C7dath16DK- z;p~BK%#G(;mID>D#HZ_3xr*f3vJru#n=%=PA4%vGyO2dBQJ9z|InyJp( zz2qw?RC0J5!EXguN}cQVVH@atTOU=O=zaUwqZsiWv zVAqs@U8yoR;!!Osh>X;6=~*-cl&z0lvf(Gmo;!mAEJ*uh1RCW(x4Mcw@44$NYW9%U zEV_D_sdMs$s2TR$&Vfx#Lexr=@NrFb_?r4lZ8PDXJ|R7ar5C$_u^IER-NIwI+uI%*}X4G znX6H6N0*2Z^5w1Fz|EzrojH~>iOz-lUFR6do~qaSBcV8hN6x6X>>v)$3G;#g*P{@2 z!pj!q#{)VIsppLgqvRDj6F3c}l?%n9YFflaitI?G{nNFNOWiT?=PSkuA`z zg4%SR@Ld!=TfW1iZEZR^Y>x<|8QQRDOcm*G8G3AD$z9r^Q7(Avg+5ltb#(VIc#rE` ziM&|hRcndVH*tJbovjc4vNsXd{L&?fb4*l$G=U#gCmg%I^uxIixV6k;km04muk&Hz zD(2jr&cJ zDy5(6N-Y0%5~bTSM1-$U63xXRK?^pSX`RDZySBYU|u6|5%z7DRg6z6>6MV!=wcHDaJ5Z&vyGspPDu`Hz9ev4v-d*y0jOGO zD5_-JCS;6e8SF0xT+{-H5}9y?Wy((~Alb9*6pm&%_+WO{VF{~2E?vG>$t7)M&~rn4 z-YlkA(7-7^w})A9VL87Ff1-(iqxXgFs*qtI=iGKfE(4pTs5s^Fr=BKTv*OCgdJtw} zEa>7UxF3@ekujAN-Gl3HSa*XM2!#A-Jm%86I9OV7}PgB@aa}yQlJ9UUyRKjt7@knW(T&Lv+K4syY& zFKQrpKplY&!4npMfhhOSczD(%eCWD-!7|E4X9w@OF*;#U1h0*}WfDZ8t(fi(&BCOC zFDsjiAbn@+4^G$QnATNnM354C4dl+kprK5S_X{E8@5@HC44>sSlZh_)v{r$@-zUT_ zW`v5)Mq!=F@+FS_EO_9aMnQz3RP@FVm_~HZ;d*j5w~PtGdEFqzFeZU=uSpW$f3`CW z>sFvPrw9m9y`}5DBQMu0Z;&)pPf)2C2`c_*4~4d|aLy4=QR9+<*}3-&u}hI|bvG@| zK>>1Q^Ki#+IsYNF+z%ljn!m~2RoY}52&>`8X$j=?7=1474jpmwWZ%bI%(--Ym$50TaAHPS&#lBR+7>rfJeVTy$*p zx@=JJT{_F#OyDno2TvUX8=oAm)vL?64x2_dsZ$svT)q9GbB|7Eco;k*0S!_4D>(HW z2}iN5=5kWMd~qbnhB`fK=8{p(Y@D{1@IQM);-7q5aMrFLBQE|yNJevKMRIiWZl>mn z4-{U)T)8=1ZMGz21$mC|ZZV5t7@N(2vKSf8Z2|hwZtnW5GV%_7i^9OKTeKi0O)G>~ z{dN0P@M}(j>Ru^!7WB$^1VwvsIij+E9ZDm>9IuG|`Yc~P6u&LOsi+aO`}0sxq;)1| zm2tI2XsQNw3tzfr3Ozwkk*D#H~U5N`Xdfp`Df^!G#)LMOA56wz0&(|FJ-y?S3^Dp2(Q_}q~H`tVDVRl-`I}0i07HTZfP}O&LdGWdnkr?(xAxgD1Wsrhj zJ4ZgzGWfFkEuSoW%eqfn_agSy)m4S_I;mRuSAP+}mDS0;uH7)U%K|KcjD5@bs$#w3 z>$<6$`P9NdL&t(PcQjd)woK{;bL{b11*-|5a|8G82*Out&CQW@##~t2a<>YvAPx3w zR8(cGsq*bcBhJ42a(BCm%$Zt|Mh&~>#klZ4lm*-U?;(zRq_J&qRwPKwDFuGQr`O;y z5O`nODVO^}jeBvf){JK9{<3l~nIGIR1dbEYs4-jOBkZI=?rylMRM@u~2(I3@YQ9KM zvcKi5;8-FaOzeEE7v;1)q0~C7(5BI3`E#O3?T|EA-O#;(C{N33h6Suys7>o0$Ic3g zj&v}zIzfT%XMss!(q_rKvv&hgWTI$Viygsy&w%-6}N|T}XYl zGg;|Me%Y;NKKn+_-0*X5s0IEaPs-l-^=!puRhpexR zYP0FqE>fg8ltOVzk>c*!7AWpe97+ieMS?p8iWk=a#XY!dai_SuI|L7bFZ6ld?>+0B zU-!z&N@ngYv-j+4_BH<>Y5a}Ye@nR&^Y-7M4{-f2|lP83apbG%9Ep&6mhssoGlnR11QmJ zP>noXFm$K%sXT06q2~13W)|4$+BhmZufwjFy-RZ{B6F8F6u9eKmYi-cZnV@}Yg*a< z;x1ylVRaVDqZxQtv)ft%>=<3AQJ$V(nk_8uE?a&_*99+-a-Do$O3Iq|<@sgFg# zBQa*8X-ok_#pRhR(_~ra5*{Asu!h=^p!-^N1E{Bj=IKWpwYmC}QwqmsTh@`ESjn>= z9gedu?TfUtDl1hI?TPxlA{%?{cRslN&FFQ|z7D(gT0`MHL~k=-c0cY~~wQe0N) z76ocewVu~|(&y6{qkX4fv3Nd1SDh4Y*4cyJ;S2oU|BioTx4-yMD655InY>aN@H&7?K8eGw^w9Bv z5@1MBakBcb3za=D2&pE>C{+;W(e0GYVm?2OsM>v&Wrl&eTn8pJ@8ol*m zX9g=AU|=FP%Ro@#AXCOjBG*aE+m#4CMpE7fmg`ntIqZf_C2eECG@Q5vGC}6qXbh)7=JI(3azVj-}hb(qc)VZG_SAwU%HANe2 z+y+{do{&7jx+1#M*0J(G_M{F`m+rM<*^{O}S?zbpb3w>^9grt;Ea$=NOF6@Kxs5N@ zjF;6kfZKVFffmw;hscx*uud!corWh|!Ig@ueN4T5Gi|I2M!svlBP_p_vJ(eiyC}wy zW~8#h;j=^-D>2Inf7LfOZ{d~nVz1ND%AJPn5-VOnxjX5HuUEhQlMROI*Ga}RysqCi z?1vOeNpT35%JhA40%lhnE->Nz6|eT&p*)QwW*sA9D|^p!|`@Qtdh!Rv)KO zk+#2ZSA>;|a?%%oPE?C+T143ETFOU;G|ATfIEGgM>PUNX;t)T6!ekO9ynz-&fftzzH) zDTeuNnDWfuNoXfsW?EcqB(7%RU0eV97U|{?4e6je*8%L{hVZJIiG!qB>*CttYx9nFF*>cCTU$^oVI}%O{V^s z4{jClx9zD_`q5YX1(BQ0sJD-BB?2@<8 z*^-=5PQrzTj8MiOMkkHcMQP-ezKA43=`K@PHGSR{_7*2 zq9Gq2PD6?ZB|koepYprwElLTciws&9R<;W$fRr_m%E{QT5VLhwv&cB8CrbMLJn?3L z$h_z=g0R*p<^<%pYGXC*v3ll_FY9zp$YEV}L9`rM{6KZ$JzcuS&*DKxIyl_lYiM|C zec(7e+XeG8!yE85!+Kx2gTDU~es@gxiL3KR8+zXC;m2y&T6(}pJ?dGS4`eW~He%zU zv!(qnvqXawyawh0XHzx3n6*FOZldgaY=s#--Bt!A3SRImlVG*F0|?$kTQ&OwnqdPS z2;^dnZzkYib+|Fs?obv|m@kCgaxLLuZ3(ow>vb?FiK9@#HT1de#m3P#mu}w;pG(Yv zq7Ai83gHly2>@S;=6A!jV$9KkdScY^-di^yW4{`p^fYwpdDZw6821nOHfPpXC$;VJ zJgDATUg==z6Ux$pObN2e1qWm|JZzmKj7Ka})jH$0m_uSV64>G2lf zD97;tG3r@;bT+2lawCYW&M^Jm%vW93ugY!Cl2Fm>3h>EW=|gQpTb`g_DD^Ur2M=wV zwN<(UTv~Op1CcsV3iIwfX z+@Yw~?3yIgrEd*IDqG0P9m%bBe<4NU<}9$7*;s+a94tt0zZwx0s#W=4h?M&cCADcP z3K#gn>&qR}ovff2%U8xU9uJqx+36QT)xXFZb@iD{daro!Q8==G*ZcI<(;NEeV7T2C z)t(=Gk9XMH_Dg@UHK>i9fqBTBAvW7R^3BHz1);IJjFpIDM*V8;S8cpo58m^^67GXc z!QbM2{d`J4P2`t6&yQ=FxiZbyi6^WBzSimQifQIw32i**sYY19`GyF6a`I!(|*_^BU2y9=hmqP$ZOED zOuffht|@DI#BJM&1m@^NSVT?PE3Sd7P zX2ZCab3IA*oU>i9R4tXgeFF^fz#Q%A9fDww{tj?1WU`vEi)rPHOw?2;o`;sYbu#+< z34PzjVp{O14|O}vbx+S^WzPhjoeghFI)d+c?E{yBX)J{*UJE^rJ)FV`=6kLj9{wn7 zjR)uVZ2&T&rP-{Qw65(~wURn!;!}N4)4mEnox}4}Re(E~(Bd4cFAO-2mgesl-#3TC^B$7`)ChYG;!wbPdL__Mv&ZJV0%S)DW zEjn;G9}ayD4$nMTO&fO>h`Fx_-}WwFZiRP}+EEkH(-9My5%018r1oh$28J&O4hG*3 z1Ul{3QvlA3;ds`eX}IFhj}Dg4NCQYnbs7_lSCi1Z39KbM;g-NlO=2OQSx6!LfI(%% zo*oSx*by*(J4>)l324>>fjP+azuoL7BC<*}@(eS@g^UP=Tb%X|EyvT_(U>J`yAG+- zW31{mNj=w%`l(XmaQlV=dDiGe-K6|ChytYxn9p5z(856zjcq)&vU6zo5r(ol1CaLh20DFV~A8BhGk7~WBN=pUp2jMH!8 z@UPmG0kOlqD)Vs~eLESHlyO4ZUN=A9ISib5V=;b+{w~wHD*9fe(^u@IfNnY#dU=Kaw!EHv`E!jOsg|?S1*eI zG{Sx`$!xU#cys?A(KN}I@r|no(*cQVBhojSikQuIw&FuXm(~y?SVzt1ZMI{m1Y&)Fr7uT_@7yL=Bm?)X z@#5X{5+$jJd1IFiUAI++NI%U50G@H zSXnYdMaez2hK*wm@o-xtL$Y+HCFIo?QFoo&b~+hjpARNQv>N4NNas7KfI^A0k_%YC zAxu@}_L2b{C14fTdj2wpicadt##+y?9uh!!ZQ#XiQq9Nq!vs=)%p@C9t%@r74-^Z_ z5w=mJGhk-5oma;sqE?VQce{_oW_^@Z+Xi6YOoxsw!h?e~#i-;mmF*gf&p7)iJnKu2 zI@mBLu%v(#>S8h1J~CIIu@j^fU;XkA`~qXwjat4`T*Wk%K60yCJoZ%VPI93sC2lze zc8?IAAzL)g9{>G<8f||BzAsqjAIyc4c&2?3klMzYRS4ZS5{?daRv^RpsKjbGq%-cw zV1)v`iwPxzS;u~-o^yi3ACbdkzbFlOf?>2V&!=R>V!JP!UUi|`2kdt$MS8I6>+8K< zHsLvL-r>4Um(9BtmC(H+-NYF007oqONLZ1=3Yiw>wO#mFP=xUo%}^5*jrO{m@@%+y zmfxIO`Yw|Yoskk}Qy7O@K+O$FPCY-~Of&PGx>uQ#dD3~Ln+ubpwO@NyEmI(+7UeG} zmP^nGr{l6y3H`1=r^kpFxi$+N4#==gN$(-uzfN?n&bL&@dMLG@I8AzjL}WvKeZVv* zHhJDcQ?a&9V>cR2`7nFVr9+x$cGM9&*TR3A%gxZzYh0I&A&nn+M;|;E(D#w5j&3?dD5cwt z0tc*{-TFU~IlK#aGLA}#r;LR+uUIQz@x57dqfLN7owH3x-n$Tt+(X|UuXn|`MQU_t zM%z1V6LVIcUF=O9I&4)I+DYY3-BJQZ5XDoQp~&A0DRUoZa!ELiW>+9!O=mU1KFXVI z=9S?LZZQ`_?D`oo3qW}p{5&%Ep@@76nZOXq;SY~~^x>Qe4u5A+lbl2E5r(~edlN0U zU865ZJoPCiiQ5Ui_iw&UD8AB3ddGtUH++z`Oq=}*BD`Or;5`%OZ+y~isF zVWp?fW96_lgL(049kWVBcTFm?e|^8^t6(QhZeWA-SVD!G$d%Pt@+aa;>0=E;XEBdB zFRp+A2=u0)~|K4fBVGK`6Uv-!IJ zl!NK89e3YsEe6YBw7BZ9gcEb?AR{^Gt?u_s-mA2^A7#xJH?H2RrHHOp$S4{9hz5lr z&T4bb4!E`#-fIn33v&{^BQS4*#SbSbesYtKMv%0d--k%!`?H2Q55^z1Qt0#NH$Tfo zlHYSAr5w6txdAP;^&{&oORP=ONe9Cbd=}=OlQOt3g!Cv~SCs=M;@h46*6u2i2a=Od zOXLIL0Gfe;>!rfpd_(pmuhmMvLWafktI}9F!aer9`a)3>F02Oo9 zl!Z=N&Z%3pf()*`e-j`$aHJ;K zwlhViv-hy@F3Hi0@66{Zl6mxm%H)xjm2y=D&&EEx--Y^_zQE%?da!eDdu=4J=P$H%_brhn zLHA~BOC(X=jM(Ax?_WrywmQ4dL4WcB5DlVd3k*!FOkDoJX#LkwK`sXy`}BD|ZwTBh zUW<}9@@MmI@0W6(CCjwbb6$#ezydoM4kLWArf^q50os5esSTcBpXTwU>(>O6Jyb~N z9AT7~$)VK}2+OZr6WkM@{Zd$a{p&h=y40PuK=>3Qs&pzVkE!_u(rhYvojmXxM&z_o zbZ_lFEx4QiX`Q1yE*IW!Bti@)Z8SP~gS%KYP@Wdgs1XwzC8h(v=Yp{cFd7d~Nd5dl z^h1TVQWp#uN9QB7Rk@9+0yf!uhh^jdgP>;uk>1ow;qBqCK6A<-fX}QCJY~MU2KloOB-6^ zmlvqBQ~U7Sy|-S_Eo$iwPV8|$#<|KB3zuXu=gy{?CR>?&i_)rPBrR5B0;zqqmr>8d zudoGVl2Qp9Oip#t$c-1vehl#kHfpv@mSmoc(${Ge8`{S%BA*iSHvV!l#?c}^h=dWN zG3!Jwztfg_&qp8RSJvZBcFvIPioUxVK|W-m^RsT+ zj4Xi7en6G8C~;sKa?ecV%X9KMF=PhZK`R_Pcc^C^%DGNxlgsh(%$*_P&PEznp5~_sEriX01|7gE^<)6`W>p&cW6%f!YVl8t~0Iu@CzA z&w{En=A} zxQs%OILp)tytQ9pm^?7=;Hy_EwErKrzkW1ND3tT|rnkF4>y+eadU3LdaEL6CS)UHv z25Sv}%_osJoMd6}M@{hox<^#$-5I2cjwoArRv4sPs1X+$E&dF_rfEI_DeH{;-?xi% zM?4rhU_E1Osqb{5NgP1v16}&sQlds$$M1A*x z7;+ap_RBgLRdV%wSZ%T5tazElKCDx(fKLQ}ex-@+g>?#Xy(=IZ==}kX#k5)dD)gxY z3<49$W?1#SdUx~G#Kc4aVuaZy8=3Vr!TU^vPq(c}&Bk!TzRpxh$sPsxB((B{C-zri z&<9cx%uwUZXn~jB8e;0cd1@5!UfH`?G3^gU#2M=z~$@SMvI#w03E_bq5B*LG4$9-)EwG;=>?K=3WlWV6(urtoFG4F){W>_!W!c(Qwd; zW1R1F?zBzhs{qS?y8y*22$C9%N*B-0F)mu&C&B?nlS~Z^s`Mxu zR@ufXgb9CxQF_cIQf3^}J$2)mfJy10$Dez>+YXvFRqFs92E&g$QrLddP_A=C zDBznf*M0XgLlQL{f&7Z7yQ`W8807612bF{Q>Xt!cg28(tSDIEM+QINu3>uD3~;!H*RkOhi@`t8 z?YY-CM+??W@D7YOr74=xB2Ck^C|<*sggI`V1H8i%ltZzREXDmiu+)VU^4d~5mZ)3! z**I<5r>aG|P`(m@0OUu{)lG8LwoKEu##SDe&g==i^TAf^49Be?4H9Yj#cb#CL08_8 z2QLuyZ-%gs_SI(F>ng3`yf?}Is^wTMUn$Y-0ag@F1~V%}T4jt=$mSZ+_R=z=_-~V@ zI4kb%V&j4M|Dr|#NnJN)8m{ITkq)+mOoyFuW31t4)rEzR z2HMrL$c@+Z7#TSn@oKxTx&ud!6z-QDm$lNgft!jrX=rF3!iNU>hKh88uFE9wl#yTh z2})&t8G*JUp<-&Tm>pv5dpj(#4M8#>%#MRLE=gNkMpkMp9BZ-`JYNffr)Dj83r!gi zLw(<5qWyjR#PQSr-A_?7ns$8+Od5&gg-k+%^mv;QD40%0p%GZrqB{+x)HZ;3Ck~_@H3ouJjoyz(Ma9U?tGm3Qy$+D=W`{>K7g61De+sim*&wJe=Qgs zR|*`h7TY^SNPH|76aEsZz>?h7c4y3oa5~H7&jM+s*vXiq95)H1WUJFH&&ZhgD*^HB zH$Oo|^4bC9y_9AT2c#>U5D3P$+vqKM-MaTz>&1&pK%IQ9-q$zIPMP?4Own(sa5E>C ze(>h7%@Gh5=Km{59*Na(g1e!U=38to3(&RW;nh`C%igAPB#}XVNAn}ZV_4VlmGEff zcSA9kG0ZuZqf~FGJ0wM}HtE{XKI_&jbw^3Ga>1ryrM_&?^B9!ha0H>Iu|LnLLLK%n z^ze5(T3I?6=#qV0GIR~kL{nT?s4BiJ#^-tej{&@g8-RzQ9oTKnF$0vn&t!15o_65g zWd3!hWVzfL#ZA`jHg#T^YqEL&U!^OfBcCSRur)*RoTofFJP(8hlWQ}8A8wZDakvKB z1=`lsqIk1=U%kIuX)@1t>$kJzmZovlgG5UQxw$!XJJzMl7*tM?RV-X^;Q4Ai)ESaA zw?}f*+ZmcfdO^{fC$s+=7cfkGe*_Bipx|~{sv1mlT1VfO7Hjg3UuW}LgUD-{?RS?k z<}bgg_tMr9KPUyH$WhEER@Idb=cZa_|6`kDH)uxDsTo!Qyf@s7NLR78KIb@1!@35I zBJ@~$u23jSla~QXmkb3b%Rbo!&4*IHtMK3NH{h0&S%LSdgmdqSu|4i~<{gr-iuf>XP1+?Kd*sOM`o(q)p! zYGi!!7-eq6LZOpEIk{txp!wMdc`$)$*9#pt3e|3E>0gZ}Eb>xf{Gj80j<{&b!J7=5 zwpL@vll`Ag`j4gRQ$785lHSnh)GAIqKa8n3;*E>QpbRrGzVWW+zD3A? zz572-j`B3~x&@7EBvhQM{ZS|Baa;KYugF(cQsJ0$GpKF5;LR~rIxA`_W;E5vg zIE3YVTT+f|Vd4FoY%;N**cR=?)qRcw)B7#W<}}&@KGlAz^pjkDFO*2w@h1|$WSmu%} zsE9g%8D|IWb{@F`F+y0$9|uS~!jEXx)46??tT_AK5H$wHRPqwoz59Mn1^ZkjfkEU` zDo$d7XMCv8wY^f|%Y)GKma{!VQ}7Vs)0*|^`;*xt;fQ{n+vHQ_haLXt?2T6Gb%{|HqF$!`*L z4Wd6}zoAa^o_|O9!QR4RZqe;}3Gl^XAnkg$N^rHAi;AGT>I zp*gpU^rHK7!h-++j+s*78W;z zsbTz%s)4?NKULh-%i5lSD}mBg5VNM2}$^1hSeF1|o%F@Y5nCZiD-2$&HePjJxeD2V-csZprrU z&V86>RJikS(Z0EGGHgFDFMs&j=R(b~o5^Q|jNhp*&0%~5x1YeK!4;)#+K=x`i}rLQ zUG=>sjrc+oX^EyT1&h)@0Wh-_kC=>%-Id$H(vlIJxwN!|QXPC`D~I*hMgP&6>(zb5 zPWpy7&dGwEZ4-nQ0JS24+%<>fWoXO*12Mb z15H`-zk{T%FC8Ah%xpN`=Z@EL>9Vcw zmmD%r-%yQ3?&t1~%Xa4CWJG~7YU_4&9rMNNDADsFiv#A?h@ z4&|)37X%kMSbUlKQ_p7xE84_dTixNe-sU}yITi6wt4i-nLVdkegcxEbc`S?q$_2hx zTPu8V@~_!*gZ5W#UD-~otYoMW?Ft=NTyjNKa`95;$8KsL3II(-+m<-Ijhv=yg!;aH zogN9B<^7EA_Ss-D`ZC;U5=gsGTXy(m<3K}mKoS~R4Eb_dD(ybc0u59JnhHsHTNks| zoa6|16{XmG+V>sp8pkf03i^60bEom;dAKj%dsS`iSN7NkEgpg;LOGIGCjNjWac~pr z_ggN^{tV}MFx#7vNgB!{bX~J0j5}AQ)4^QP8qro*shv|KH2>kq5df_%-F{7^Z@zrr zGm|=Vzm~E2<0^|tc;;#Xwmp9W8*`c+#TSEbkdoTGM}>waGR_Xk8mrl2=0lk4;)FTd z(wUw@_9~4s#}4`Eua%Yq1IIz|ae48I+_rs`TeSdeQccxyQS5AQG`Y!n$xQU@+K0%t zU+i)-cIX)ODhY;)-Dq|UzJ0(M4$E)}0aqC?A%@`j288W|-G!+pVZx%A7SG_zI5hTZr#WAN~jnebJ4X7SU@e9*cE znSpF6S~Vl$rI+VJQSmTk+u93Ashwwc>umkt>YqNJhnsxO!T?vnEP% zFxaOv4Z7<%L?Prn^-p31zhkQrBFZCTdRz~sm`Pr?pIJf?du;&RJBe?(TV83KKD?b` zqg^nKNIzFn6|&hxo*>n?GdS}7Cidysy!VQghY`~YY=)L5(R6K z^PcfoPW^zgb6@21m~}hu}>9^k7NAZMeSog#XYR6U-M$E>*t?&gaqeJCdOrVt4GD>P=CFdn>ybv zMmp+av`GFmPf(lIXmFbMKcb>MwGetooJ7iE@;(1dmon_*z~08L%tB@)}%v+aK50Y4x?1_ll7Y}+fL(Pr#?dOe1?e6F8wC-NkZdzo}HV{Y-e27Ft+Pu<>{n;?{uV=@BSrye7tC9lfh z2PGwO3ptaSmDS%9UKKyLTDo!*j>0yBr5ZxBI(CBDJh};Oc_?((&d6^zq84mLPnTVU za3A(eGMHb4_hvkPhE%S2_}z}Uw4I1@1s`45?f_Ku;y(Y~#qGbz9Pt32O^=fh`*{X% zVpAR1YXnwm5Zvj+X~VCP=a*57Y)#R^`-Fi|3*;XaRtj^Wy&anyodIiA-ZLzBu^mA_ zWnb7)-i%2PWjvf63>6!=6$YS=+>)-GV^7S@8S`2bb9h+=n5|LT>y(qUO}AFx>=8Xk zp>6R!?g(AkT(0E`t??{>Ve71H=?Ob7=XsoAb@i9~?XtCUTh zt}O+XOWDV!cX$tKON))r@yr9AQ*d9<%KZ@MIiW$9EJ9DddgSqI*?S@4>FN}(sA6CI zL-xmKJw5L0qt>?XX(^I_|7)H~IhtsCN{Wz01OD09R}kM4HB{V$s=amq^fH3FMpg#- zvrltbcl5#|aJ=Hxu2u5iEK8jd0k~Wo)sRqtHu(|(n}5ocsV{g8AisUaa&aKcieYRT9p-NOr4ezNBvN$|5!6_a8#z*XVsvB z_D!-O-33vZcgdDQBY-3k$Bzlz%xPGe_fKRYMAmswvloAjv}(E7mkRI9!W3{V4!zWD zz{S2R(zBN7t`LYkmek(|0XdCO)ys1i84n*UExP4>-L-*tarfEfEt%@@~W#|*31i(Iyv zto@=YxWp#$;0m5mF)d*DYauqmPq*+>HM4@!?^1zgeqryBKIv8Y9S}El)E=UF(zKGw*!O zDb-fFI}1H?FNKd55h6}=sSpwJO;D9jZDnCVcL#VW0=ml7dy)h<@}yf`J#!6F(PCj@AogCLTDFjc!dlV!jsgjnrX>j#+8x>Q3Zd5y3nxYAdXU&(|x><533 zVU?&N$>(^!1SM>o#Z>JBI&g)hAR;J=bB}i!t35LmM=y;+d`1}UIW9w=%r(N_=?Ob9 zWP6hs^S7MxlLGz{`N6pkmhii9eJ9sZGWX#*o45izIt0EAOk7an^5HP0iK}FDq zQ<65S$`JYiKEh2LP@*waXp6Vn4z_)+-tli`1^%FV1@~2xUw@FFQgXy6AEe5MIokrK zg?%q8w{XXJm6I7~;@bvRb(m+|i&1Utx~Gl2{%AEmFb=o1j+KPNm-a*Uopg_~+kLGz zon*wg;=kVEhO4|Ig2SJZ1D9wnjk2Ah!d}XxmZGMVRAb;Xi#D3>Q-Ong%DZcj0{#vxj23qBQHVN!Ygz1w^RF;Wa$XKc_}2Pum#qN)*xvRjrAo%fT+G(m zq&BSjV}yG{THfApKaCmpq!?4*eC@x*2xVkVnlVeE2u+%w!gMTVEz?aL7hLv%2g0R| zS8_$bCFptez$I;>Ue<_5v3l)(;s3R4xPdD_qNxwyf#bF<_-`;!eU#swk;Jgojj%B@ zbLZSMR>KG7rh^Nc571IL3H%|RUBb&dpIL=ntRt!VRGxmHnc}6BeE|WgizqM+M zO{8CO!!ufWugVN_$XNL*!~YGXz&O)MBg-i)Q7 znfpxWU1Ilo?Ikb`ErL#p<+_7{gNL~?ZiiB=L8x?>p>q0~2`_TdxY3_pUq-TsBvibG z+85qJtF&P7N2ADnLVHUEIaD$;CbqYGAnOAD?VEC8Z(WH`+d9DeD(eof;6=>0 zgZr+okAHHFx0>~uEnDueZ!3h}2f($Vj`|d`0;&?8794ckF$q`1pK%}Yeu!tjQgr-I zOcIak2iRX(*ZiFrbw63Husp50(141HibZ}SJVlv|3i-Zi z%>DZqYe!R`rv}quyX(oE36V*r-6%=LESF(d zbxE;`!qmu?C!NqCg}Oz6=wnD!iqC!fO3Fr^9oLNK397MsD}iSn;jiS{nym?~iN9X^ z6qg+E#ykKGXqFEE@k}rnyjcnv&J=Spj|)j~@6vT)pEN0*1j}O`G>dJgNhd!Q@WR5v zs2fqS$vN}Ws=1m)?R(F*{ZJ`ifFYpOSX)=FS`^7lb>w}og}0aE9lX)-`n0krqD6H+ zw`&|Y#NqFVxD1yK*A4^B^lW53b0@-;CD#PZ%h!J#K8`iNLYVv@;)AWiRQQ0qTOt-M zn`rLrJW^d(7l=8(AT%^S?%)~``{&P}G^@uiPEI{h;|IuS@?@#(v1$m;OH6Xl?G@WA z*FN>+0fsj>b7B{4K&gf{a;{wg+%;VxoZ_?ZZL(qrsX2;knBym_2wew7sYV`BpQFx2 zPMC{X7)d8v{sH$re7!f%e|=<7=&qp_^ma?GdEz&dg`t!Hg0i}hvB(>d3|7@(Pp8!Y z@zy|dAZq>KT2@M@gdVheZX-m)Rv1A|8Sf6E!SC^_l-`~ti7g#z(;+miQN`MKEM(|%uZb# zs8-&n>Dvtv?F``o>GOPzoz*E>>Iipp+GY|Btt)hkz7_Qm=sy)0X!9Rqj}88V@CnCn zvINa!JuKcF?1EPC!VB38q$}t$ONirj^`02_5&P#hZ?8=nViSl>o)6h4O9W?6u zE8U4k{3MCYlmYxVS@gM7R8U`jC|jVN$aeG*KUS&&lF)#9n78B=y-=N9eS&&^tQ`weYafY^3qTS%%_Sw@1vTTltH$(#VxkC5bL}+IL%2?I@-|2_aq@F-5(g{D5xm%T6nk7 z!LvP6wB|3KPxI}DPW{3?(UeijrqFrLG{q&JXU z=9<>BkWdw)le1HjOWV=#_8eR5jd&Dcvuk*sgLR*r&ulsYjnP77Cip=A?h;JSJ7e8YPdizR2+!+DVwdGNDX&(#VhYXf|HRC?DPUMza~1Dm`8L~veXQb&GR-(^OeBkXTE~T2Zr1zZt zM=OEmkleOy9x%ts3Mtn!ba@UB&qbPkLZ52v{Fl7Yj3UN$53AdjK}RCjP`^fG3O-c97%bEvh%+=RbjX%S7n;+Fl>0iC=b#&vHD> zezJzlV^!9t@am(sZ1U>6R|MK9K(+*Frm&lwOuX1>0wvD|Mb)Ly5Ll0nI6b=2IaYD-W|1P63w#zE>H*CvkW z!wfAHg+|J17futH3<07SGhmk;r^uy17?Y;mrZni6?T;GI&Nf4{2Vm=YUsNTn0KSkS z6&23LV#77Q_&h1O^KL)}8~u3<%FZQm)>lO}#p&QKE`XVdRV5dAC#?XVC&J^vo3dr-V|qTbZ}_B39oJ|{am zhk5+%FwI`*>u-r!Y8>~4(vxSS%cq=#rg zx23!>*A5joKnBoVHC-(GvAFGVSJdDZXk^UkFlZ?)05W{-6-~F34;#LhkK={iCAib3 zvF_j|oGn4VThhJ>_%Ke#uy#TF=QY!OyOr@x$T9S)eeA4qmgb|tvMuex_(oql`m?TI zdi)n<*j&gZO5<>ki47fdQ)NeQKPSUE{eogY^p1}v|5K!}q+5GVao64XSp;(qVsw0Y z#e4pjvWb>=v2SFo6}Q3LZX$!43S=<}xg#E`IdQBXpJDe6RkWkOp8XbDh>EH`fGk0@ zO_$cJFM^a;WmZ~9WcJQdncg(GImxvERlf7ZOsqQY&r8gp%P+Bkah*A{>-RZUkV~VC z_KNA*3i6o^M$2D!lNUV_ne(xNG9W7ccqS*Rz}O_5xv|2k5%bn(sD!#5a=^{=v$C%w zARHrxx=N_&@7r?_V@6LK-K&x4y4!{n?ELv$5KN_aV#=^b02POSp+)KD`X@OZf-*1Y zhP^|`kM-MPA;I8Ajr8$_er>cdITShhp#J`c^lN=396H)hs9ss`Y16g~9JpB@O>pgR zHdBrI61O38P3bDmtv~JjISgBMa`tL(butb7I62y#NBqcV70Fs>K)r-Wf@3=vInho( zgetG|#_XjZLOPrDv}?$^1Y5Xb(OUv^<=KC+uTBOKM1`P?P3rFIzBUY`-+Z1|@FB!2 z?r`iKy1x3dY}X$YjDG!)N~>;yM!G*U#<3MDeL7&?clKXBTx^7Cgbrhybl=+WjJ(4s z4_KNJ+~u3H(nsn}%*ZKyHCkiuc&fei1tep>XiP3uw)^V6IChHth*jAprx?oUw`Xg8 z(Lok6k-59nw><)3F?9WIq5?lP`}&8qP||>_*oV858)&`hm;LwOj!mgR;hhZ`IQCAN z_L08umW>h@GCZPQzP{HmO072gvp;&8y$Pv4Jkk;GP!Lw5uH>XYZ*L6}nWp(1FMu)( zb_ER=1`~-Kvi0+(Q2Q#W)aK#MmM8dYZ$>zNZtinwX5(5`Xy^`5clT0x*xSM{6fMgM zUPK~QOqlaWf426O3eRb^_ZjMICd19=WR2SuennAl3&}ZVLv;eBgtd^giMM|9Kzt)d zr00u)aR~fSe$~ACjpWV=!0+P^PXG1n*$4!Ymwfd%7HW}Mtyl`f{te6%PF z^)KbYc|IBP@hdis^%vI`i{%G;$c_+)p-ZgLAIgri>vD@syY9=C2X%-0gtVWa4Gxye zy5{+ZdKS%S_O_h5JZ=!|ZDV#TyS5~*jL+FYX}*8~yuVEzeB)N8$sasHH)B7)7PU1y zUztp+#2}o!i#a--GP@Lzk2=B`hSTh)C*kd5$~?ezDdA3tFprf1`4;UeH@C+#4bC~_ zgPd!)V@BV@#`;BkeV*JoscH+AQ+Pkhb$Gd8cvi z>LLwB86J|Kn-oC2*PoVCoI39%pohT^2j0leK_!p*2GM?A!O zImm`gaO`|X7j#I26G+EX$_5=c;Gtf~BXz^TlVuH<33Gh^ta)&94ai)8dNUCJcsagMT)4xFH08iQqt6U!rS3|>bkrWx&y96&}5n0=?4SRoG`C0R!j z7uGp+!vsY>9C8GTxR8APWRw%NmEXq!bgUcjqb`yk^CXd#w9HT5xQ??0(vl`hZb=h& z4*A)({5tZn%GEnNp+Xrbx39CaNn3Z)E}%2o9Rl-_58J6r6n~@#Kb#lRS(f2XI7-Z+ zSJWdXkc-$9YER(DKsk9W`>QBh0iBcw*T4{XaYD|;Rq*`*vaX%pdHN90BE;$967AnA zor;t{X*nzpN7e)D4RO9bBCehSWv1N#f42KVHcmZYCw_1k4A!6X4t)!?C+dQG!p2TJ z$=lzt>YiK3{>Y=8(Mww9aG(@o`Q#oLD$R7~D&zD2iV>GRNgD z);Wpe!+58Ib#~(dbqu$>TSBZzRx=QDmWh>f{1Et+deDRpI|;>+^dc(U!jCjxr#R=$ zmxa!sp1e2UB#mFgS?134=}!o>+ezq;qh>K`jO?jEfqqa9~0?$PY*8 z#X%zJ5HD$|3yvt0vV}Ckh5{=?{GgQhLC%40JoS_f!~+AaD93|?GwHk_H_D^TQH~Ky zfSd?XSd)h7&@I$Y43<%rzcPb7zQaa-ejQRiH^9u`1==M%BRp{|EE92%mw4eiUjQ9` zvW{Q_5*LYSoPK^8kl&X<;!R9BU`Bq_t*c-0egoPT(nEW(=ZpuYemS6J9pLE7=-G}+N;~B_Nhq#@82eVs`Q+1MttaoMFi=0{o_E~rz=3VEaCA#d6)ul*zCW<2XD zsy+JhNEz6FK)AR|JvtMC|{J!sn3Exqh zon8pD(h1_r&BBDti9d*!4i8tJLFx~{{d_*(N?fN^rkoHUPdKU67e^n{D1G8~C`8dB z4m|06^E!@}@x_+Gd~wGldI6ufa)}rC%k#MM{CHz7VxKSg3SKQ5>@otcvS+SBbK?dINS*9$* z72wLqZlPvek|TNI2wdm@5qrslEYp4u+%-Oe=H*FAi2o8zl$RFxZMJhl6m| zA!MSS$ctzH`&N#a&ePILJ+nzKW_NmdzZpp7WJK+w`}z6Ym^b5)%=$ z5jvT4Oi{jHN0ce*n61BHg&w$dTf+4{OJ;1Y!|>%yvR-oddy`413+f)0C*&p{lq2gX zq|I|89dzV{?!b*$_;o;5uo-7JRu1YLDW}Z<*@-1>6Z#^^fj;$c3Em&epgg3NIwMU^ z&VdEWK^fFj(&u3AmFMziY#1^F4?4X*DKVEiNgB2#VjyK8U7Ree_dtf#tE033Wgxcs+8FXi>4A6#@B{X+De@|`-JE5E2W6vtIQnfGbg&KZM_?UmQ0cXB?iZs=Eq7r9XP!Fj*f=E66<4-^tzgZ@FdO$9Cl80 zB-No7bj06(UcOja$R~*dM_N87&1+evKe;2*lGb%T9+F?u^Q7e_aeND<$K|V4e3HJlRauNW%{D1)k2NixAuIU_tXFj^9y!qmw2LcCZ zke5!P%*TN!b)#NUIQw`?K84Pqo5T?GvMgeacsRMBjl#nXt}G1(gb0+0yiVCVY>q4k zt2odB0et8uA}S?@x(OBCfQej4goyyLmg9Z$WHt8?MKRnJn}l=82yhjP*0Nguq9pJ zvu0I>IK9HMGR_KRX8#zMj|DKDHi>xDjUTAHpT=^CFYQz6B=M5vp*`>#<*^JnlGFon zER$t{R_4QXzQA@z8YvITVV@dRchpDb3HjLiLjZQ` zKT7^lG9w)}L%U&nl=*nh@=-SFgZ#my40cAzAp08RW!r&{_{GTsS_Yo?cV$?jmL;NKT&Y=tI9{imAnfmr=XXX~H^Im?Ko|-qk5p3XuA^3xS z|JtXCa^4)H0)O>90FQIr4;`?(77OT=b}}Xs__ct2d&f=h%7odtIgnzh?@@N^;F9S` zxzHqzv)fv1%k`hVPg#^z_Lw&RTJpmL2-<=C%7B!o7LGH@kE?&k(+clNkadv8c}qI) zyvYKz(s z`Dp2&aIgVP5jg0RgC)dcz{50LrxiHhA%5VCxVW+)mtW`1>&HjQDEUGs>8YRZSb&E1jB^Yiu|r^a9*;V#IJ^tl0CjP8od+}GLYz=uTtJyo1Mnny<9Pb< zY)?_x#o+~7%E0^_IHMj5VdcwE=$hEZ(GgRm`+9JqgOd#8LAjZSPJX_!WI5!M??{`Z zZ2>blE_mYuDWc}Qz(Zv3|Xc8$cy?w8NLpblkwCAw4B(IyhtyP(3z88GM?w5y~Xqu5;M@! zj!{14BCoh3b&tZ2HtDwu@CPo78T*Xl&H96kyyv=pFTw$)p5P} zeA+_ofX--I&(-_o7rMp-2zIp;lCuyWrb}C9T)v~Em-QN#C*L~`lH`x$TkCkp9`~M) zG-PYZPsQ=Z4gfhwmtf*X$9N1R>7O`>Jit_u}}!=J=g*cIq|Ym8A4|~v*lf; z;T-w-3uHk13d;9si@j!F!*ZhXueZ;L7Yc!n!95qWaG?D_J^gt9nv*_~{Fijp%bPdRaEIX#pCI>xQKgN6<`>5Dl&aNl8rpA2s0Tt*Z) zpX@j#@yJQzyh#mN!! zsKv1w?bX*S4y$5hNxKj5WlZC`C7pLV(FD-HS<<=ZjgNFmkIW|>HI^YAf~5Q;9S1r% z)@#730S74@v?P%ggOWmu6j&XVN3vk*r(xDd15{*`joa!!U}r z(c)Ma>>sFWsd|vlsoO@}3(rumT!qEGgmxOm-R z{ymoQX&$M&Y!l$m`*&J>2=PZbz5}0BPhw1ZthysWM)GHWBkjq(_|%q*0IbQ!MTkG< zArFyM)Ttcc%5$GdxA$_|VC6PTbztN}=GgHc2p#u@Q$B2_hn{aHJ9FQ{E*(WUxNDw% zo694g`8~4qD6`O;n=e~Z{t`Y3gk`jt|yKVc$ym%^_R^R%4v&H*oOnllT*Q(C<7p#r6&!@)tR70W>x^2;(x z3vH%ks8P-(om?me(zCU@Vtsvb`|d7Fzv{2Z^S~h2$Ih3Ze1PGx$wl*vTld%~{H#OZ*(GJ6d?=eX>C-v} z;*U5o3hjUUmh20p5Kmc&U!1Pwfg|fIN+wB1`AE++=JVxaI*w2NwH-Ni%X$s^vQbA0 z{=haLKhj9v{yFOz@w5k=lP@tRuYtAE^T7A+ddO_r8uV?*Prg2;j<@Jvag6|+q5{j0 zN@JYFnsNTI5FhGCxsjiCK|3MOBS(&!$*H66G9TY4o@gWBE8nfK(7w@p>zmh;eRxEE z+7IMnUj-h#f62)&)*tH*j38eWwutuw%aZZrCG{LUE_hiXom$96d*)H{@XuLaemvW@ zACGIcU-r|oY}OOY<}t_vJp&IE{mnxAQHx`Cg3K>#csSVUhQ5_$k!J7yVEef1SIz1d zHlo}Br+M?(M|Hc;h%zsapurEaooMe7_LZmxZ^B0aj=TrCh?qafvG3N~U4I!>XEE8r zXg-%7Mr2Oli2AFGNLu6J1?POrkp1@i#&g`Wo>P;vW@v-Y&A1i$fw51UBN^usj;DNP zn>i}Ia_u`6?QOvms&3$3;8SCik;ibbKX8^=+WJbV`Q|55~!3Qm0(XnUmj6rkI5GP1!Ihh0s2I zyTd!lq!M&+Uy;aD9{Ef83Z2sqh(lE0I5 zo)_W~(6*Ui5Q7&21r98(?|b;p(6K7SruX2bj_J2|h^! zY<$zY?$8NlJ~x=ZHNXz?qi(itY%{%>`N#ywiiwWkLqb8M)K#7{k37nBc`j*ijy4LN z_Z@=sZl5c)gK`XzWj){b^W?UhudKH2PP1YMBnYJA(M5WHv{^b=bdYx4y~j;7xQRgF z)3Prkf1fvWMIaxf`!tTAgg?m(wl_H3<5pi6<_?)V@13*#?7WHV^O6*oL{8!*`ANBw z@Q`_N4xZ$}{J@b|N_yC=bDR!k`vsW}UVdGIC*sdKJ$JGVyby5zx%uYRle|cyTsQ|0 zwjnz1h-Z-f594^GUc8PwCZ#j;!CTfT2PeoSU&!kR^5@Y{$8}sjwqNGqgckZ?Oj`Wn z`qdc9JbNFU&5Q-2>yCU;?>mmSo~aG!E3l#sJ#jw(KLqxxeDA{pd&aHp2NRG1g>iN= zvNgCrGEUYJ#v`zS;Q>3jIvY^%p29ww`7mKVuomN(K9`4b07r=>?+N~-1Nii~rgj83&AI<2Z#Et=UOhcW#|F1n0o_EJRFUxMcT-=j;!5 zJuX~vk8=|FoOtiy11{gnz&XZPnJ{R_Y8%tDoU^NGlt0d?;_98uYpmmKtaDQBux@bC zw71!J0eCO_Ddgv61f^nV*pRnm%F{tOb2|yBjn8tnWM|smGN}CI?c`# za%XqPmV)P~B`=Rf&Sl%$Kn4tqhU^O0QLJDd3ugPxLf~xVWWa?jcgW%}Ix(GDLAGyG zwX%=yKjJ1rOt6^YvIT>QY%k36a(HI&BX&7y_991TB--*_5O@U<(f<-lS(jXmLN1o= z%F0|rcInh{KuX(jxq>~j4_Om%ceux70w5v>gKjzUVJL3BvJQUc6 zO_$|Dc3k^%)21mmn4s+L4S3W1a^Q5hRc^=L{SgO4TSKf@PmV^U!-m%P_u7@&W6l;( z23E9DJ_pp;bs_`m=7?P|#^BP}PmJODPSh@O8riYgEOmC8qnTX=Ik=D!WDqvlDeYFC zXXuAE54*D!Yx^TxhI3@a;FjA=K}6f}bq#GW!_yry_84MjWm=@I3c64}S;t7vV7W8n zXdjudZOY!k(at(E?V5F)?XQA~NMwhML&t3nu!1atA9@A6;Tk%xJ8o0Z&i7m3^8{&I zAX{Y>LO#?bc(1c{;QDD>N3gG5cVj0^u=9hw{QJ-1&_LMNqD%+{BJO!j5JCvkGruooOH3>b}mlU+!vg%*??J48rg8TS z>mTjRba%OX3~7k*m|dNPO(Nvne@JMLxZlV+MiOLhvpV3u%1eE^>yqd0z=z6JF}PRUr;9d4Wz@yAiG@2 zhoiT$^#Zf`zTY4V14nuBn0@}-uAag9z>jZ(CsA)tX1u`&@3=;!`_IHnQm)*}IC(y4 zZDhTj^JyD=w2OvwgB3kY7eqPmGH=v52K@O}>)_n@&W!Ku;|=oe1%7r{f-ghdj)N#! z5a)0ThMSIrE0%HeGQKDC?twTAqgKM1AN7v>8wQuG3`_Q{_w#Nthv{)VnT}%;8cCnz zd~9sm^z<&9_Q7C61N9C3OAV;cs1P@?6Ge+SPC~?`BOY=lq07euX-Rqbb}A=9;>rMT zv>!OscpHD%uB}ZcooE1W=<2_aQkIE!F>4)nggAYY$SZkDUU58qdU@?0Yw119bdO#w zqr=@BIgw>vlpx=V5OEjscksBX$sv zz=5p05Puw~GX3Dpyg4|N;g#XRfwagLo-!YgzRYmeIG7-YVdhmQx%jP0j*H0P*fX`8`eWt~3s zE38{=uSTGpm<)70A&0Q-D1I)ZpmEm){-_Jch+$}x?H#5wBOe@Pgyiktk>~umdMBPd z=1>F|lp`W3#Nw{VK6`Zz^5QXv!eZt6k&MHQdjnT9K?hygzWlZ<`Gx0Jr%p`X+D|po z_Rzm@;)pBOvhNJ@qCio{Y(vg2tOEzA zJ7a$=9eCuG+m7KVPxoGE`ZM4DMmhrqz|N8OXH+r-YsAxYan2*1J~)qa^Z8eOHCsDcg+YYJOa~QSn^8_jdqD+F~5T^+Q%K@e+N9 zv)vZY;MWJ^Wu5Yzv}v!>+I0o9wASthIcj&P;2CTygp-rP*dv@%^o~h%$7364TiBZJ z2kIc>XMXk~=M=+E{Rt#V7k3Vxjz@-ovPy?qzBrP6kUnRB&~?fF7Hz#?S4EVLnB;Qp z&6XttC&+xfBn{3XBPJJ6mzZeq>rl$dywpp6CNQM4yK5!~E;GZzdG{ygz{knu^(~x0 z@^^|5BdI@WDN`km*8Ey4@Mg;!tGfZ(n=PFT+&DSt!3`a60HzZFq_|3fl>-h4B!av4 z9WnRayvwbs^w@h6R#o`+Uk>oFvUK9{SZ*vYVAVnfDbwV1u#FArq(gdtU+$d(gP?o& zjm7H4fs^DR%S1d@Oh(7W)UhDICwU%w-l-yH^{HP5D5NgTLAvRgV6ZrrSxM{b>o!=a zhzraISy`8|9>K3Kvv(U<9L-F?kRNS>*RUPf1!M%z`!cIJupK?%<7OoP{n|6us`cyV^GKe zuEd4<`ZC~}bT}stz!p;$9d=MjTj%N&WdL?qNkee78SHRP#~J4c#8~Qyc!>?JWyqo3 znoaTZ08==&kQG5XKaiF3V|5ukksovu-&6r<|b0%g+1|Wjj8Ne=W=Q({N2J(N@B1Iu$koJ1{cyA};;n0_{KK;iv<9 zFC>GXCzCbolUZNRCN0Lm(YIOTVSQ02;>By`>*?q)=x+`@d?+(Hi`?_}eJO#Hg)7*$ zuPgbmUO`72;`8jz_UwBoFv*Mauukp$2j!z)$p`t_UxDXt0B!pf8DPJ>hoNm_hZf6} zbl?dd4;>tr(c$^b;!=oD6m}qFe;H=7C$vq*5kp?%$bJ~-alHMy0WAaMX8UEEz|I+z z9Xzg)aop|11MnlSZ`)lWQT;G>Xbi2lonl0AINSakV}+bYS1fH8S9dv2z|;l0NI06+jqL_t*VLOLf5Q094$ zJ|(mlcTdRJ2e9Gf4kwEEd@`Ilf5q2-rXA(Tz!hdXf-)`NIUGDV?Q}fHk1a<&D;>pS zxz9RqGk1hZ7}&!>kOSv1G2#S+e+=&74B(S$ot{%6vtR%pJX{&XvK%^1q~&!usgWIe zk)+^}j|3DX6a+ouoda;5{grb;8ha`yMh+b~W;SjP?#;0oA`(#cy1^CuTQbLHqGxn~ zH-qm~BOVSA96at&o@B^@fepu=E11HgzPWda-_V87alNQW_GNS6h zm&K>&qy{I~kUlcfV^($rl;N&}Oh$l_^4^No=_6EIwG}@6K z9n0(y%#<@=9eMCP^SBMGoO;T`K@a5h9e>au{Ya19zPRTAv$z2iCP~(=8#0}Pg9dVK z*$}J0r|vat%sRX5 zhBDh0SIib$=0lnK#sF$$gi;xUEzqbV=y}}c5jXI+n~mIdbv{h6bIbbhv5ag^W5y2z zjuE!8x_Ztyf8d1xxgUHu7<989H*ec$_TLlSNnmf7NWg$CoUpR1U|q9t4(7~uJ9wg% zU_f}H#h7(1<={Ee_ICw2T%B3n86XRAKwH^=bjmpt7=+R(0Uc$)gxJYj*SoUdJlv3R z-h*$>cjeIe_RaDrC-lHG)sTmFG2RzAp0A3@An=g?Q$ z8uCIGv=P#hAMG3Te)|EfY^(EgJq|~o4!9a?OR!0V0N3uEJ_QpZu8u^TLYdNbC=<%1 zT(VC&@bF0*9%;XC(L_K2r{H~01(z`NYc4gXsx1A+IdB7fhCi*q- zMt{k=15b%3`63>xo!rXJ$CnU~0KCBifpzOH>>qfb?%HgZ*S@@D?S9S<2iqJk=#Dx> zSeRRKdD$NWYlpW>MZM0uI--ldcHN+R)ueN>>?9bTf2M5@Zy5>epZJ1JSPvPVz~kQW zQL}01ab{`^JwebLV3II&=xkd>ox<^iZ9z`(q@BZ7FnH@~Z+Dxs*;kF)j!fRe;PCnF z2=xjq!4rMhI@_m3g>^P^z}|oM?>%H5vbKSGkk92>Zu2jk;zX?G`J`c=4=1`EjDX*McYRiwK)+{T>kQ z-9_3z;7A)l9PJ9{z^H4`-qUTqpnmX3Q0^lu*q#oZWxwhkL$LS8VQY(ykDc5B9b}wp zv*QmNFyX`d#J7KVtBdphuzLX${wr43ar<*G`?U8B-gD7+9y<~|wt>eJP`|JZ+~1Dd zvBezOhpqW|JY)65iYyB+3d4X916Tw+(>|NQ4cGnF0mC4AW!pN=GKF)^*CEbd_8_VNXeS?t_xdumkhOY1Mr`$8ba2)3B8{YA(batc` z&|%O{CxuQUQYO}4Y7WnxZB8;@aD+~0>nuX5W&5}Qo;}8=?GP3R&v(bRm2>Sg_g3GX zpD-)qKQeuDzszweB`$dLdf(b$?B-+Sm}UN!7HvOdh2|pxh-N)tmqEU~k`Gq(VEArea0)DVl^lr!_1Lc=-ap&acr?27>F$n;BhD>2yWbC-} z*!3>n=ZWi(ujMzp;ZpY=j_&yY8|b*S<4;WQfsZ(hCvW*{`v~o=PCwK|Xglk#Faw#1 z8GMPsuMbzQCog418g&k35pgcB{bO5&Y-qa(pbxk0yBHg1>uhOZ*7$pUkiTu+InLge z_kKEf--pfbJL;4L?4*piW*y@k0kkth_aHXC|CV#!0Ud6bU3O5ic5uK7Zmx((|M+$uQm9B6ZZH#BT-y0)|O9cl7( z#|$5w&t(QSICld#QU0Dolg{A`({Pdrc$rO9kdp!6=ktupBjeyGGH@`3?g)5caK64R zzzYMJO_^C-I4Jn_*j@_*Ksg8{FCLvd_W9>d`&(;!I*jF@rwqPs&?(eK5@xzdOdueP z+CjtU;Zr_NChv>W@d2j+IsK4w>4%cnT<|94od&=E3a<?$hqmRhr?3f^iLdk z(7x0wj51Spz&gJi41hS`AGMPf43M|m)1PTPQVv`@=VJVrTZzHT@!-UxF5oP!TR-Ho z9Nhho9YkXHLePb+SSJUQ1-tLu<93X|Y3=Iib`uqa>dP^-e~^Kp&+0v8vJNq2K)b?( z83xmn9f7k;oF}c5wP59itPB{0VgQOjY;Z2i!nF)c<58x$rG6Vh8^(mgJ$7dVR^RFT zfSxi$*&g}wn8QEM1?XY7J?$%&f6!0R1`#i9nDY5$5GxkLKBdcUTOa9NlVfY3VSY~T zAWlvegfwBFlEr?)+MM6cK<~nyQ9DVMZ3l>3YbRx3W8qu2>=`B@yE@u~GBahM&zx97 zd(LgsQdXO2{5F6%W6MQA*`#+7_zQIi>X-R}E80JXmviq|i=4 zjCR-2(Y1;{(kLtHf#-;4pzR%a_#qdCc7iS3YnGQ+)hY9k5Bi?@wS8uM{~_1bxdICw zaNKuTC;j@Hb~9Ue^!t>kx~5IiPJ!XB2L;d>Zg$A*^q=N?I&3^_0r?;&e$XA0vY7lt zAa8e(@Q*sny6)@iv$x%#zr;jJFcEK8-L1pB*5Wc}xAgDNZ0mQ$;J#w(3eM~!hL_E< z?GyX#eQMO+b4RCE>qgNq!7*j?P1(Mc^+l{VTCW)Rp$-^O#^_{V>%4#A-cOqTE<4F# ziQ&w*+5QKURD7>Uz<+<(524S5qYj?9cinQ!Zu^Ck?0p#TE_pc9Z-Q?2*i6p-4fO!I zITpe3QQLhN3JZvrnG^eNwhibkrv@CcBcs7CH$=?Xx5KaNxzV;KyV__29LD{gv*!}T z9~^b*xHCzv_@dj`e<+#`^jkJEm+dvyUuMQTUTGV%jT`u|+0y+<=Xk&&F!wd4=dCSASL8q2P9iNDI1Jbkki{ieAKxK% z1!w-?s1646IAJvE0h0=Fj`HOlvMb&ulQ#~-IQ&@{@-g(-e}gGe5qMEc9ZSM{@fKnn}DN*z!%23ivoY)gv;sgx9@0aGlP5I=;{n| zxUeZW_V?Z3Yz%R9EO8E*sSB=MyR-JUWF5@Wv34h~i?@H1op#~~N4qOwhq*E=XAAVr zOcHRy0Cco}oD&jdYz@*q2~LOI|~`M?jhazAtvHUmCe=7LE- z*9Ki7*7xyjx5Mg+us>tl88SEV;c&El3MS4byU#b6On@9GnX4Vyk-nFi{_Y#Y2?po` zJT}d|&84I6Ld+rCfSqWxI>?uBVuHm@u4Vczw0qbC_&`^L6Gt+A9VYQ6er1+daK{UD z3HjS=)F<*H4qw**{*;MjLZ&^|W`U0try`+k#fFM-u2ZJis)!r=#*8C_l@Hw5@C+kv znh4!nH-xJZxGCd?$$$YV0tO#4U~mFj3^0(kU?&B}_5_X_cw{?2w`vG^Fwv1DP$t?1@*+?l$s{GhT4Yy5(?Vovk96(FF3h7QB{t-Nq$d5W?pNiH0`Po^w3xIa(^TjpfrHp7#vSapT-8W((?QFLcK@+33AlAY8c4JJpVPKX~rHfeTlusd$-8{9hwH@S4+ z;nR_aJQ6p?5kFqz$a?ni^!sVFiM1Pt?I){(6>sWEyuk~39`T4CbKq#u-?RN94T0rz zw+AME5Wov{5Oq&y9gNPdHJDV13ZQA5KWd+y?+ABDz^)LmOJ(ZdXxMiLFTD#UhNSG| z3z;z)ux-2Dv9xy~v`?nPPFYWs4dW2tBfp#g=ZLG`YrC=&7z@k2W_~HakUTq{dZ9aqlZNfM9mD6DV|E1$bO`w7JVTT`QF@a;?uZri!QSlqBB;+MJK2A5fBbl? zyJOn!`p8Ul@BOr$$hpHz+7;0x!N*X_jk=Ka8pRK~x6SVhlVpD45+=r+_> z2jAqjX-l2BQVW)-FVqdfz^zYov@<8XH?%RtFWJx9&dz1mO4qgB7=AY%Tiux-v&X72{ zSf|+Cf;ySEyDnrqlVw2O&i$WsE7h22!-N^bO83?f6Vx-d+_P?AAJSgH2lXt=^6i#1 zV*|gRQ(wMKp?ul~&Wo+jI9Om(}|^Eu)`ZYdCLJFXyJg-IbXG{ z5Cky+I|U%a9_zIE10xPXF!-QT44DxyXhlBg!oUF$ul*zVKu!dd7d41r9+bU#=$PHY zXLmAm1kNHbL;zND!U43@pLmjwPtQRJj*y+09dG60;0g{b`9NRN5LdZX7;>C)d@!hB zI?4p^1BZi&1K`TR5eI53-9w@NNCP(b(b0q*VK6^#wmDT${|N4p0lVUZauK(E{iJ;v z+sLZAbHovU&>Q8k-Z1c@?U5Xg3GIP)2e~njmo^Q0q+_5zx7_Up+2j#-METuqcrg-i z?QxR@(vJMWG|Rvsm$naw1S?cBKquO7=e&K%;aKpzFWZTvmFI19vthl{wwX>F=QT0o zbv{`^LwT{KnE^Hod!l_s*)nLSt-fqe`Pzxk7nq_=KlspSIQd5!|H!(foU(3#=b5Jk zwugM+hd{>z=b-Te+c(<`(xEGLrme~L%XSU>cWu&6h9VvOH}#I@OkiPx0`)BO)23M_ z?TPh+^n8IU>rphcDcOHV(IF1~0@@7&+cd7Ry2p_HJn|ihKgyx5 zY>#x_IY~f1J{Gv&u-}6G*i8T)a0(G{A3Oe}<8ta<@<3k!+|gFn+T(rqW_Jf6KR;_H zx5y)22HCF0?3=Dow@Wi)W`kXc-2H$F`@|g^gULGBDOT;J9NQ09m{hf~==$RMgAOJ7CseN`Sm{`I6024I81OfdD?=86g@z3rI`+n9J z%94E*;(Iz~&2raJSRVAH0}Vb1yiY_;@w{XL*|v|J}%U!v)jH;WY90}1Iq-h#5yW3FTD1T$XAHQ&r^wO=zYq@qi&VDZ}ah$C}I`QG;}BKQ5d5B zFet`=*dHAE!jP7_^DzNwL%^ta=} zxXo&kI{CS*4-8TnqGVtijvbQ^t=gR;(<^<>c8YTR!GAer)5-M*-No>fm?EE)wGghw zlv66Lv|HG>bWBU-nN~i?hJM@+{sfGlp5z+!09{MnH6Y7CTZwYmB#k_m6BB$i2Rz`M z$sIy6Puw}{FD^YU-tR9__XwE8;)JGeyK%hAjVFGi6Y;xt==?%%)ExpRP<%Ow2`3jY zDUXQ~PRgECPHnL4hEh zcQ{}8l^Za4&gU5T>A1$^KpJJ2D|@B#uN~QGrbc%cWDiF{4)P0T5Cm{Ud<=c81$lT% zny7k+;u&U)I4MW|2zG8VAj&o%D?&ayi{e+QxKj0w(}Q(VuDnt%^5n5x-f8EDtkRK3 z-NxaQuk0jwhyjm2Kg37boljTlb(+pQ7V}#wL~Uoo4H?e690=nE?+;Go4PAo9fjQ!k zN6zw-x7?nIn{m{tmS3JML!Ltp4oLkM!O55ZP%iL|lbtJHl0VP6@#L3{x=F0=UBxVv9$}H0<2l!%ZK0*`^na9KR*S8{%@no#!36!qkN};j}7r)-63C8h_ffq`mz!; z)H5701e`Mv4`M*xvfV*0DHCX9IpoEouZNGRi7>fCfJqfFm)a3`qCopsYwEuB4+(xQ+^xB`OVZ;7!?l`D7l-M}F*+ zK$9;Y($U^w-|XwK(#dv!^r%4mDIaOjR`4A5rkz>aLmK3xY^22sHtiO+0vcR{2b?qC zp`?z?&$`1kF=AS|M^0vtkB?#0eF*YI(a|QTKd>!?pX@&<1N3K`!VWD2*M_VE&%V(w zpV#haH?R!YcVqPT*&S~-4{clC(^(eq-)tT8Ir{*otUK5h>kM{-$FwrL<(V}+y7Y0o zgJpAYFJV5)1VZ4*Yixz5EQmkZt+)q$5^0bJeCfQeWWEl8fifLG4t_Cs#b7!vARR%{ zfDaui@{##vI^sctl}a2XADKs<7m`WR;yejG<&}A(WS8mr&ZBSveIcI62O2ocNwAPI zATRPEFt5Zqu53wzyp#dwNb_w1@o|B9;e=s@GmQYA?qi(sq$vN_mCype+2B7WRkyeO*Z7$4i+@ zIR{_rSW5n=JT2$EOAt8dK^z7?ypfYe-t-w)=!n6E|56`0DS(?R2NM23j{_6G44=+V zm)E4_QKl7po`gq8V+T0E4==;%TbEr?@G%VOtuXRDiEL5yz$_}h5I^RTm@|%BQON^? zHLfZl&L5=Hv4Z~NCE^I zSZ>*n-Nw)SID&`I$4{5n#h#NlWQ&^!psh;Y%qQdIIr!1>hOV4IfvptE3qGX5QT7RF zubez$d4Au?c=SKgfoHt52gb$mU^&jXNZGmIB|YJIV=n5a&R0)@8_@NHD!ZLm5?PV&vNjWd8d!uAHo$^HgyIe zCs8`Of_h{AV(|byd4vL#c_R5iW@%&6hKemF4wqv2fL>ysB!3}2(vppGp*$H%;m`J6 ztWC&r^PNLhUrx{?o_gfVUkKBvy!mvIb{Cbm9^zWod6zeK-Y{i_$aEQB=o~l9M?Yel zxz`S4d2^L{cypC;JV)L}3{q~4NjB}w>Jc?SPE$u&b}4+L@>6E6cz`blYQX?1s~^%3 zM_R6cxOnRv779`27s?Zt4qhDCgN6<~>HRc1s=-Qeu1?HT?oqA+!ok6+5Ch~z9NNj; z(i-asWnYYyubnV|8b_2P+bQcAysITc5jmMebXv@hC9|Ks>H<9NM`mnY zkTQFWx=$d#>=S7ZnDA$a(uH!N&M|?+(A%{V4uY}Lv)(2RZk#s{j(3`wh4ye)2ArS; z@kJ1<43!gt6|L-cSD%+0oR{Nt`Fzv3hHSv9Tny5XB@G_LItk98@#&bqm}j`tkh>5M z%2kN2-qWQXKK7NCz0~s9zAI@H#08TJ+!>LyO)_s1{773#U6ROAE=@~1@A78o51;_3 zOfMB++>q(Ob5O+_eW^SfA)g#bK;K3UQFZ}+q6UPLR@SxDUD9yiC)3k9$DmFQ63G{F zei~)PYEGJg7;zv<-i4fD>AY}o%Dk02`k1gB;>GKFI7Zr@&auk`$xKT;K`pnk~;3pSI*J(AEUv`-}=@CeLh}rS&abPIr%30 zRkoEV8Kk2fl^zube$bPg{V4ui(Vj7Rq9>O@A4*?GCon=^C zUAL}rDPF8N6fN%V6qi6tf#MV|?iSpNx5d2_FYZooifeIqC&2>&PTqIF``hQ+XJ6;% z`E~LqSCT6;bFGXy*P3HK<9@tm=TvVp2P+IP&^ty)%uVKN-PU|0_+N<&hIhM^BKuzS z5Fl^KwsW%p^4UK+>hBkpD-?b3TmpWdm6s$hpqn)}s>wOhYW#Hc&6g;#m`MNQ#DSDb zzxu@+`FI8&3$niAS3Ze*rPdD`A%wQI>n5gZi8T3WZOvx9rjv>11AtZr#kB?HV-vp4 z^my08hNfzwvq3GxhW2$Y&1Jju1a@?Y&zw_Kj20%GMNPm{;h%Rw0bFY0e zi9^7)Fi9;?JG2QkUUKM!v5bJwtJfe_zM`jnLAKwEO-PQ(k?mlR5rF#nmfT8nU!G9&6jCqtzA{Z;N|QJ)>R<-F{rtSp z_rRg#F~s!CA*03VnQdcri7%uHg`c$?pBQ#)u_=9W7-(#whu&A+WNYhAHe_8=3oKlt z_w^&BCFrcRkC1KbvgI33!(^2C4zSvO7;Aab*WBt>iLfN{cV>TUDo{Q)6h-Ykc{0^q zvSl1b2bg$)C~>9jEQQ`ls%`vnGkyH?qKsi7Hr68dQC)W3hQHST;j~Q8sNj^YYCUyl zJF=0&vMSkT2DrezeP&W0js*Ad$a&6T zo0%4O2KUj;tlNc(6MFxTmpOE=(~g1CITy0!_b+WIl=T zpi&IIW8jBgkS0dRRGhyeN(33|I~H3mtMpW*cp@`naFwXE;VJ#};^8lOZ)z~%I#){_ z(_L$9Gjd33ie7EhSzfy|p6(RcP~w*~bb6_LL=-RBRLz{1%Flw8C{*hd?zSdqEv5RY zR*toEM|rP*E$LgQC!Z+Q)F)8;$qjJ*-B3!^6?g?WT?yN>TC8b5#_)G|&a+v3*I1*36=rl(!Krcuyy& z{J;WSL3I8zy!0I!HuWQ-QF3%;=BMb75@n5()rz=VtRj<gpkmOVcy z?ulk_X$^vntSH9sV88FdZ$%5|NJ$Tg29?n=(DA4RjHphF^tn8uzZYgDcd`W7_BH%k zEeJM}fRyZnX?7fZ#8hNCt$z-}mi_;mC(fEV|cQ~1Dr+^1kB2L&L!uP3n1 zUvG*_ZN!~z)ZvNsu;eUnbdi>s!98=Scp~w`Jb7C~6lhg7$w|BB7LzXhtH1As$Miq$ zJJN98TJmMU#w4BFu(Ft(g$CR+!)K#Avd1n9xNA4RV|XktiedUA*lFU%-6Lc5cA+qo z1m_Y<6%Dj{yCYdiw<+vvzeJgtA&@DAoqm@2;{3%opBZr)F;R2yr&0sQ%53aO^v2My zczlXK0zKjoPM&^G+470c>bhnAvip&t+1&9ksHSLbE%(RYd*X9%J}<=I)WDBhZHwT~ z{M?l75+9^ibf~VbuGd~~o;lHVd~sjGecea|^3K@9^Nts%ntBoZud%k?Kf(EPW;+sgbAww~pX9%Z^%IX;xXVJKC>CKY zz-I7#Z{wsUNkoXc?Jx?)d#hJn`P@Hd^|%qV*qoQabvyhRHsf7Q&kw5ppu!RMF}cqM z`e|UGc(fBae{WRrgoPaI3UOQUB(hQfPIfkGKII(y(0&%^aw)N=ReWB5UaM?jWtMm`4H|Msb_Ybgt^@=2(rTQ}7Na_f)z^*~kROpQhvH5ZrWLYQ-W96w#Y_#kd z45*+w!P|yMS_u=98{8JFQH;9!{8_XxqyO{A57M;RVy46>>hJpghA0wo#bNerPHDNp zZ7eRQskj3BRb4BuWeG}%k)B~(&$i21t2HCQZ38LZRM6I~ zPN8ksYfV*@GvG1$sHzE!5>Cjx)9~T2oT?>8d<{;n@+a+V^BF%EACrH)vX&B}c~}@+ zg}UWFs==KBu(y<};Fwf$3`%M$p}>bc0Nf&o+*G#(TWv*9UHE^qbjWCF@o|HjvDl1D z9>*3<0f_&m&HiOt_VN8qUt}0(DnE>tou|>$$_`a zYxX&hTdHGxIS;aKLv=anm)Jo4A8%;>gTnon2hiW`1!1sra%MDvwk7s{e>=gQED@gm z2W$Qg-{zOYCBnwvb_AUVvJaz+-w)5`Qk#=L5oIX;XK(&vbc-Oll${vHJs(H`xkdO2 z|DLvK(SijZ9IjS`^!nkBgnzTw{u6-<$D3lJbUtR=jb*OilO~&1h>yJ8m{J8PMT?X>1Dr*e#k55?R-5{{jkr&cb?mV1RIv`|!J5HiahTdxJ{s=q$6 z&xQCaBsO9i#Js4UKYs)dTG<1|nMH<;-7v0X36z4m{_+Tux`P15YRp^K=VNPihd3*p zva&K`I4e-;6uUJj?pqG{iw8X6t5r&l zkBH6meERE|8}N9#p8@c{`K1{OuRi``2(=D$>;(67wXLtVong&5hsYcyJNZZYz+EqF zDqHVI_G;%lyMsv4>Oha}sc91?kBo5Ktp6lzRSM>&A=#mc^8IyCm2*2xAkZSX?MO!$@KT6xm#%8kIs7;VpU4i5$T+u1OoYUM}-!yM!6MM@~)#Pu` zKRgkc*QY#jOd4F`$D4JoYXVicqxd!39G+M?4sH-nKb>&*GS%Q8bpg%)MjqN%8Ay}gWX^3jLZt(Mng5O!ez@oMTwIzpYV%AFyz2_g@R)Oi*p9)GqG@m{yj|tpOjFu%B{TxM{(R+f+3G3cYxKXoaRu_;ut4JkIg=E)!e_}AmZ1g`LzYL}6p`Q?51#U0Tr#-K) zC3h`rdq6CQzw*@arbcTg`7pWdiF{}I{#%nM&*rOa7*Y6d%~B%SbUrp57L~jz<=1k* z-9gtTRsJV_Ya+L6x9x*%YxU#od+-#0s7AFas%uz_KfqhDqAmJpG18L~h^{9EaH z+NoT!H$UgRR#n&Q{;Y&+-jvl&U6m#t907f@El4=^dvde<27Ex1=u+5itASh+lhU?v-tr*bX`3aYU9)@`)|P3wJKm7Vcvu5|Ud!WC&*F`v9SvRLcakl1dNYJH z6VO^|W5%q}jReIkDgZq_p%ecOjB74d`W6yR%pnVc-DDDK&=3VH5lBn18CD}?&SVS4 zDF*s)eUtnNv0u4y{5*B1!RqOt#oCimY`l7wU)PSHupYL7%lh^C^S$ka@%j_F?MMEs z*BjwouSr(eQn{FojUNRZmg;A#ti6U4#ZtMPVAoCSZ=V0?s*>yOkEyPoKWT;WRKaFB zMDc@6jS5{+eVfnCwJUVwdJONjE_(~W6WD$smBT5?M}jsoROkLdDny`QQLm#7?NOUa zxo|e4BP(CeJ5&8mpumSxtz4DV3WbrBD%a_*-fC-wciVfXJLh*0!uq|s>+y%PRVmc) zxCzzVH3=4{oR!%+ufSiLg}4&DrraG;fo8z(7qNsddb3382nFBj*Pk80{2$|}XD(4L zUW)a+p0U$VsQKYf^Du1jc@ZLcG+D&BJDxt1KEh+w?oA&*1}*f8D*Lq-9!VnP`D!MhIRt?uF`1> z-dz+Ck>CY93b0gnvLLxgo9?Z69WK-amhd+Ht?)qp$H&BoD+(uzz(&tdO2$kHFKS|IIx6kTlhgVnft|@7x#Q05+Hxex6E=!`&wn-`Lj}kYf0K!x8we?c6hX`t zr}wT$Y)2x9`OQ{h;vc@tU)qeuOibSks51Ju_nxe&=W&n1-8#YF=&!Z`epyn$Oyf6) zbQ?m<`2~o^6kB>IfY$!NYW z!{{RordmHOXrK_By3!q(x>DwPKga&@Ku+)ve-fvW{_yX8B|x<_N(id5vGJ?BYh6M> zAaQUrZIIj9`}GXD#DUlX?Ptuj&8-K>z)Fj&u=wS9Z=@xiU47N&wDxmBl=L{I$U&;f zxIvbT5};p4jyoKXD@k>_0_}JY&J9>nU97cAzCG_;W<(M+mJM%%M0~2Zo{;ptjMKI0 zhPAjm72mu@5s8ZM`JExaXSpe|rrGL#*lcgRL%a_*t9&AzI83M&q)LsBqj?CyJc5ny zw7J$=jH{h@NQ64Fy5w)iM+PI`zYD*xw1xEzT`eUY!VDvPC1%xoEZTYLKa}Z5tv#C} zc8L09*tf@1Tmm(L?O{FK6b78q$Imw>w*2-7(yZ^A@qe-~%AmmZEQp9@{qQ7_czjk^ zL`j7QQ#~Cqeg%^{^-W=0$IbuITQFtg?NXG^oz7G zD1L17_GFp4?OC`gtlwTar)y+ICpx8C2XRRwZHi{A-{#CvB)k4fhM*ZAg}1g5-GV4l z@0IjqaCN7pUF=-3O8*5geL)nn>279MJ9_N=@xBaYIxt$A5?@-1LeiCgEaT$ECU1>% zeZUM!NNF(*ykm4DX@MarRZp?Q>(mA#WSn@{0DTOL;iPXq)0oAYiU|Y2&?v&Ug@`0WXxS-B!&J5aAH|8+c#XAh~9{7>2BT8Gq(;9QX` zMc~2YI`B!#3Mcrj=W}zULOl6k&x5vp<0k=P_B1IL2lzjLuZYsQjq?>5{psh?AJ&=n zruBk%O>2~FI37ym7~W$;&0UwXrSIQK8)`?!Sn|0%*X+AWtsNvAPnVRYye2~OH8Wyy zp9K&oA)pj>3S>H68mkD`eJs$F@oje+Ei~)(o>~gx>TW4EqG(B~)LL$PBXPbYg2P#6GINE zF`bQ0hUy7mGIPWG>z?K)pp~RlgZ=auXC3NP7T;^##KtF9q;Is~*)LjQnY+hL_WLN$ zz(35cm1MY{f&BwknSO24-*5M)*2SA3USWUe%c^D$ua9ooNfjx{pswsaRclwybz(Mv$M$vU%vHhelu*3=HfC#rVNRP53qnWej0M1 z+>NDArcj`ahuwSWp@_r^(>J{u(i#f+VI)@2Y+{yrZkUem-+R-FY2*a-(7~- z&!7uqgEZuq;<#r1$MefsEh`B^zJ;m$pMMo%ga@f0fUJBfi?7rYlKh30NC#XSpm3El>A+ zU*bF|ynOq5L)>qPZMroP>@VfF$6jc8+L34A#9>Q)_(0=1;xJECVY3bu}{1{*tlbd>T3UHuix z__IP)dc!&_gBXMSE;3ZVb4DJ$^L2ewJ5%&SoH0rkfx=<4CVxe8HMl;Uh9S!9A(ojx zqjAHrHvD6**}#B0d^Qe~?J8T}q|8!3>%@|(^7f?k*Fkwnj!55}>Hb$^fdVd9Laq4T z581c!?~Tf=1bUz+Ivg3(b6lWrD&^Z^VjJ;*^3AzB_z+g588x8|pc1Pu%nL;i!)Y?x zCYjP9-50Y3>wc?P)d5@c(HD;8Ixzz2ij+2GUCE5ppJwy&K9tQzb3gWcF-yt?%^@Z} z6~$4IJMA|aEvLA%(I|)bNR8KTzwYc{uMX|}bZUz!sW4ykflp-4ooYQdw3WOy1*ARm zRiL8f&J)sDkvRepgBQuknRy4M#=kD7Ki^ZQAP_{h8uwj(`)8dYIw*6{j03Zb=vJTS zdkf_U_u|yIWflGRc)>*Ns>QlBpS}KBRR8nTY=N<{eDp{|L(UesQWAf;Kx;E>&~VI< zE7Tiqa6X#O*~6Sti|>BkQ56~1yX&NTId;6u`sb(SCjOoJ(?Dwri{pMJP00j3@}XDf z=30!Y4!n>-fn3--&3l_VL&T|rZtv8&3kspA1jVZ)M-gm6(i@8-#5qI6U}mVArTRwV zqG4`lOo4ht8?g^^cv7snw;`GmZue!{C#`l=-S!v9PSbtL?`=+hF}jC16%X!T(AIAj z-8>&sT-cq~<_EPPF`9%w^~B1-LUNzUJoR z%b?1S|Ki5~y)4gC>;iZzV(1mhUD1JfI%?s=49uCMI zf0;jG4kTtrNHPB8;*D4vwx>7-)cPa$;9e+co@0yfXs7>k45Ki;c-Wm?<&Yxi!qQ@Z zox11YbthoI<(qk*C?eeKO+MfiQdxO=9^gxMg2?%xzfO=3?T?Jxk$kEK zlK;wA{FQGQLJ1bdxx)74wf$UC^cp`bD8~mcM9$=&H12mi6D?|L2u59wPBtqH(58)PHOFpVUn-EsD1H|NZyjOus7i`nUgM z^U>1yNcW}9w8B?^8_)eKAM{UJh_;m0dFMcRspA}hO58nbJVz|mv|lBem(eJh1DTlP zYbdX^N~-B0la(yIk-oROGk@Dc{VPZH&v5!eiiE!+6xfguO+2Iwb+yjZE`2%f3_DqL zyE>+cw4F}~e3x$U;%zuWRgYhL>o*0s6Vre12;8b#4W-fXnP&I5C*$r!D#uhU6gn^G zzA-7K{i{zPDn${FICnN6#r*JE{oj*P|8!=fR+<(c1OI7-C`wFiH_rv2CVT&znOq6Z zA(msaCHpUrf`4`Xi!92!0;SE5M@lO$0}^!VyB#k=t_UyKJ9McrcZ)J^!(`vh{Ci67 zpTotF?E>`1OS??{MrS179EnO~J{(=FBZ$zr@8+Z!d#FDhES-q%bw6>pPsE^s`7p8FjJ}nV$;-eeA7m$L|p2;%E=S*(F*T4Kx6Z;Il6CiJa59}{A(>o zRGL<_Wupl{S8VU@?~=^>Qc1doQJ>ttI=ypDecREdu6pz^iR_}zG!zc9=Re;>@uJ1+ zrrv}dz!#D>cfY3s)}r=0-TPUW?yno)sy=^WT4|kmtalh}GOIHL*~Zji2?kDmVl}9v z{@}Eb0I=u3-#sf+buxq`{0*_whv9~ zsQpmzHluch%=^!iwNw(&6|60$7Y+kWM9!&^D$wKjE1|@TNVcAgYl>{zfCXX1a`bw;iUE#Nwd+>xN*(`< z$1}fpCrIsw0*Ohcy9bvE~1={vq#4yEaZ0Co<0K%h{e4%Vf z|D~w?fHYxCRx0p;^#dN|4@ly=Gx{wM-Rhv~_WFcy2k-X5?>e7X;7GH~?`$8_()w#) zGbuZ4nSO1nUB$nuif6>M5Ui+R>$zR&4< zo*!N_{kT=;6t>>U4{4wU!3kW^ZJCOwsBm&wRlDR^I-iPeM=0qga>$*D;ay(37kK%q z((hnbxA^y7n=Y|XV9kd;p1al6Q@Ud8v}H70zm%@QZ8_DHim!K&C_g$EH5%zv9z1hL z{pOt+Z)P$RN%R%nYH6?1H=VnHFRqHi#gB7zm|AEc{+FSEKxVw zU2LuxjP_N;h2krz7sOmiCIyG`-v@q$)AZHSf}`iRzYQK-mA*neI%@Z^xj9Zei20K{ z9nxey&BxWrkVwzJv5>dc?uzN(5x{8V(ocLvhjkxdu^)#MRBZA}j;yNvJ4Bqud}iN3 z-Zao3_Uhh#NRi)l0Uu&m3uL6)9}D4NOP&5DPcxZx$b&4dcvDtFb3T2#P_Hn2yDPM8 za~cu%GaFA=u*x^{d)Nh^8JNPhS*gc3n~UporIvH9h0crGFYqasTlzh&lzlAdF{VBO z=ZMQ&-N%RDDI$(wjQ#$)Ch#fm9Ha;EYx3s|96;)-Qa^D&-S?mny4)%wG)kwFo7@`V za?+z{=W!FPc($N(xO4x;eUac}&RlbFD|PdJ+mq^J9kpL@WI*n7Mp|6zgB`{x!JC%lxp>my3EcgFfu08W$nn{Z5eZ%L@C2=a~c{e)Q1CUIHLRLiVLilqCo(7ui37ELL zO%Lmx9!ReaVO|G&FZ(TL+-?DWeO#3=e7mSs6NfF4&j@C?-kR5fOQQC2!t9=INnZSZ8Hh$E&9&}c)p<8qk9?K{{4#+^qUTF z*fy1}1!8O97jONry75xii*VxjHZi%Z?`Y!AvYj_n4?z}JnDooX$eu9Cr9*hc61LL) z*lh5^WLC>zT{5%zVm<1kSR2ZO*jBsH&7oDvDiexb^bAww8mZ)1q4vA5Rh+?ea99tP zW3&&|@gfK#5xS=04@2YQLnAvS@l+uQIG$hsB6i{JcqKd~-h+3}1-fZ1FlhQnq2J-z z5%%D|D_P!Qf`CA~s{B?)`}0th(LQ>g(?K15Xg!Uwhin#s`2Yd`D5_Jdx@U>$CyQxm ze>q#tv$MHS&j64?PyNY4c#gMk*bWZ+yyFVPZqFGs7=-(-gT%qMYqI$8=3Udfsh;q@ za}{bE+BD(bt4_bvS9CZNO{>0q{=n45dd@Lc$}=oP~VQ(GkJXltQ~`_Ids zo3>0Gy)jxsNyP1bli>ZVaPo_Q{m=vkle7)Qz**ujRQ`L6rMW#B8z-=O?E54ysRU#Y zBw<^>fxO}C71(LCPdB@fW?;_Ls{4y;JGW=ZotcJSJ->qA-awZ!%P8`6X!5u2&Bp{1 zt*R&Fa4#9LNYQ3YJ;D7k3b5l9vEKKtXe1zp|J>vR2*wILY_R1+X$uIAc;N^h^j0A6 zLIZW-?g%`4Z+@xHz4S%^NRv$#SIeZ$Vw}!BK)T1RY#1J~?9euXe30i%(-gb*9UQ*R zuQ<%FuI3J}=|tCB4#`q90XI=HuW$TjYlpJO#i-k-`a*K&kOyy0U$+SQ!cxzB|h_|=uk6=tl~#s(ARNH0^D zow?!=EM!|#(7*1{012S023EGD;SdIZeOfFv2m6MaK- zVISK-v%;*A&eV?RO*Vo7k_-Vm_5jfg$r~L3U3lV5l$&>E*%^Nhp=O~}SaN+XO%g}R zlfX)=Kf@?7c;IE5{h7S`jEloGDDLA6U@NM@elS<8wa9K3Uhd7xm(6S8Tpb+MFKxc7 zzEHVm{Wh~iak=1kW%?g|Dr&r6O^kDqRp5K)ly) zOdAM!2Oq)I*t7Ptue)ShQ7kWyrT^TkBZ1D>X-qkC!psO8f-s+FFY}SuPf*9RL@2yr zrPfHvM=NA^j`Mhk#;{>NK-%~5M0#Uvao7SOqTnG0yB>7V5QVU9M{3O!QFQ}!Jq$sOngI2+M)c|Jp#_&7F|eF| z7p8EKMvgdHbCvaaS9XOM%J6Ing|rV2M3xV{HA2{r=MYpa*fJ6!O3zXMY)v7kV)T*w zJ4@cZwBZtw|IRrGPgP3VROu^c93p*J6E7n7!p<6i$b zg=6Ac8a2y@LY*JVZSlc536j(s{rQ;W$MaY8SAdNnMGPc%?2$W}b%e!$b^_uZ>zA-Q zIBy@GWfrVLl;ol*S+C6wlc#>Asx%+tJ@dM&r#V(?r$2@l^9G(N4c6K=arp@jF}_Ev zerwFP-^mt`cTamCGw-cnv{?Igi=uPe!a@Q&z&C6*nAyVC#P|wzJV%0!&ifm+bXyQ9 zorK)Mt;D?q)2kmHV+(jfjcPy`65my})Xg*5!qcioaa9`>b8U)i(y3bx0z8_1Blbo) z5*5`2xse6Wu8Kk3G^i$=2O&%PH^p^7B|`aB{Kh7&U?@=z;*--o?ur(mo^&|Kn-&DY zGj{YwxnrL0rtC$k-VrVUCXr$LaHLLcnXg;^z>Wg)qs1Oq<5{riT~FyiXekdyysd+|p|$LUVdW zCO>eC)R<(-ky9?0dYMM~Haa5oM$TeAwgqE-Zr$<+hs#K%c5(1Zi!l=#(DuaZ%WUb_ zya_uY*Ns%Qb3LsyrU(6@YWl{Y4a8SXT|PUi+m?oln?b1e1?n4gT`28KRi}g*6Lf)B zmPrCjQ1_Bfv~sQ_06oqqDy_I8TW;l4^~a5eRVfI}nPC!Mx6#z*shqua?=h-Di?^!`^Bb&-=m(Mt>p%KPIt>Czu3iS+xemW<~;!xcP%}Zn}ba~&Y!k? zCSumFxzR=j+WdNB6hZ}~>3mv$qKOOInO4;Mo$zrOIR{$HK45(cmTi~)gPE$zUumK{ zI-5r~i2DXllE>>leI&G2QKf70-0=uHT-PivtC z<|2>a83Y=xz-!d%el@j1#$UbO+m}gQ8}o!!?+&!WUs|!d*fq~eA>`ynDO65W@N9Bw ze-gDtHZ}WQX0MuB^|O&9L*+pZ%ip}_Sk3vxAM~y1q67M7hH_x~^5M8|111w>o|xmc zzga>VKqTEy{Oz=4UNuDNUDglf(89qmE&8|1M%>Q=^{{*&eoL55PdFVr5x&!ck{(EK zXo8eRR&27_L6Ho=L5^-0^?HnAFOF@#Jm0k`HRY)xxB=jIb9NbX*6g%!Az}v8<}^v+ z-aCakfKGHRu`b+RI!l<6dQYt7mU`Vf*57rA^dY^U%tr8Z1|!BoBhj&6p4P1Q>mE)Q z7G7sIaRbs>=Gfbw+Z4O1@R004kd;Dnzp0-ZnF6V`R^#@h%m-w1WPvAA(usp{Kbx2U z1wJ&K_#6unW4SXK9ux;Pk{uyE+wihTJmpTjt3yDdv2S+Yyb ze-)PkH}a#)M2=o}ms9JCf2y8O*Anu?@4=!XoZ}clA{~WhY(mmwM5nN4=c$*U%+gl@ zXshzm+fU=JQFP@&qVBOY}b-vQ}CV+A~*IoIN*W@vcB_8JXwQkZqC4ng#Np}r}eNJ1g_EnH{y~H zGXo)t@L2RYTVmWtSJxM={d7>7GFIqF1=q!^FQxMpAD%TIoOQ1oxi8p3w7+F z{PN`E^w5~}#$2IiB|M=0aXHOrTH>$!AO6+Llg8(*w#zo{b3nYe3_G@h#ATA+=l;UL zyiuFLa+*mYyANfu)g*}F8EuRR&vg%pE^T#=4mCktt;qg2l{#Z@eF zwT;D}JbbH3Ww^UOz1UlB1jl>H>4JNRHl<62r838JM4oNK)X1um!dYRDNA>Arg>L)9 zn+(Ccp|oliJ9o6~5%i4gyfQbhyV*EN(C(0R8ZsM6=$a0*uPsAPg=|;`0m>7&R6E21 z8jkW;gI`c4?8fSZ7D7jAsSV+gO>YjAIY0o$n7tDu`i1v)sR-IU8c}WLPQ1Qni{alLgGnBE>%?%%=Q(X*V!v{@nJuvU zX%BjT$K&4XF)u1Js)fp4P8;UmR}|L#ZLvyyl19&*6#}S^;yps;>(<;!77F9RALj@$ z%@O{toH#D-2o5`%J z;UnM{ImE_zNdh*9rPrURGXVXOlO&DQ7EK!ibVsaQEif)o_tK&*N0eSYtYfYbA=3HF zmA)_b6m&m<4koR}(QiS=>X^nn_5q zJz&by!+Q)ewN==sZ+h>&W>0{kzMy%CmLPU6vTw5Y=ce6yy1!~u%zGdL$F9@jtRD?~ ze2r^w?Bb@Kjs(-pxU*qLJx8XS@I6Go8~>g^UfpNPN^n)d+b>>LxLjcTQ7E8e%j^=_ z3ap2m=G!u__^M7=H{P6O(Px`9b7$dIJ*LTFmlj%P&ox1qM}otO%6LkMc978TZq-4_ zXSD^NmxtB6nxg}+69Pev=v-Yq@nJh|8-#C+jKbUI0<)V;tE`WE5W6sPVvBl3RgFRNh_xMWJ|=l0SP zG*m1xnriI}G91oh0zXfi4xKm@khbNmih2EH zB_0`6UUo>uvfacceKiCm`BeRY8(pt~&jM-6^immF)6pj}562>|uKRLXm%gzWroQ#* zeNUEdAEy9+sEBtS-99Q5#?B;b!^Qn(8JlOE%q>i1cjWgc3TRDKj^S0v>SuP1ADI0J zkzE+w9CG*AVQwbE#V>lunDw-FhO+rA7M*o)L*9fD%?>sSe<{FIvPuP;C>R@_E;dcr z5=z;(M}OgqUG|zIZb1VRY6j z2zw1`ce~s|!Qy03(Im0aUZrp}KGb3H%glOIl#Z-qQO$>OLs=4ZrCmQ>fzR#bBxecCoQxPG$C4N{RPC z(I%KwA*LEO5SDt--HH5`ZC9wV{Q`S;Z;x3xCca#dvzV-jH#T7~RnG-|4Qua~ z10M@LX;Hgj@d;LtTzvGb=Nf)Sv?DY#GRMdeupbXR3E>wKeQ~L;;orOy+$;<#5G&X z21#@CTn|Kig(o$S!t=!p-vI}+Nx9;@^#$v?p0}n!vVWMrw=AClJoPIw1@aK(BCwnq z1-3B=m0Laa&-TCfJ0*F1Zvi3XKJteNqM>bcA51LRqjbNpTN)KG>>grBSXqEnW)4Uo z^GjFB_fkstPBRfW77ja2lAp&Z@?Jfk^H$massEOpB%Uv2%AVir^yhf$%&HuLe3jhP45c@?Y<&FjqBC^c!96Y~U$( z-Egn6L;Ipz@7x@q*&-%SOAw}yo1kr21R81Otj%l|ZnaJ?t`j5S$f}`+K()yz^Dh;h za?V3(wq2%`@9{A;wADQ@B63q;D?8TtWY3spfsgs85GHI=?#Ce{#4yCKCfOUQA*5A; zV?c+Hw1KVn21@(aLe`6GITHc9a;CG$$iK|WZd{{A*qqezzZ3t$WV$w}PYnNrN)j&! z=^+}W3PIw`?&+7f<6F|3h&76zJXjdkPh&sdAEJPISkrB_Fr!C(ojt5Z17FPuh%uORAB#S}omw=2=oJQx+?K&5`{1qEKQrWOPsN8c}L=k^tew(?F$c^vf!f0cr9hF7!rSl2 zs%k9viOWo?oy!|I(Ot^eOV&uSM~XF*=lR30f~T*eD1W1)EbJcx_z1 z0`0vPt8zs!x8JBYcc>#ZL&j=4%cs(s2nF(_tltePj0ffQGLofDBbgY2QFD+T!a0Jv zR)}-QQJl!ce^gz`3!xeBM)i%YsVO`+45o@b6|>D_&}5IA?7dn6Rc+(gG3xO(*tiyN zqiv!p<^~C&2-|OsW<6Az_U>=l-u*xvZ$I2~G8TSqc6;~YgHACmDHJioG1g_4$0SGG z<;cX5Qf*3ZY?Uc?H8++bpiH-tn-uYNWb?Yg~xBb*NtoQ zz5tR#m_HeA|4yN6-o_&VaI^q=SbwVvt!b+5Il0kUs&FFDX*Q#rw@hvCuwZfa_Pd~c zrIA9VuHNb+M*&WL?S46E!3z1t4mQ{ja2cj>*^@cAe1s~!^wt&bDdOTBu$${7gc!Zx zE#CTq$fCW>%#howASAe0Gemj{6g4z?FX^vzPcmnV3jsP97>b<1Hum|R9q{3 zbG0+xoW{39Nctmzn&YkW$eYtj(NAx@t$VF$7~YV~gVYuWo|)ENZt)1V6M58Mh+b)~ zId_Xc=2b}ep0IGG@P-@CdMh+i^1R8~y9~z1T|vQ7SCNd2Fldp%$P`sS^7atK=)9|6 zN=hC4QZ$ezx8yTWs3F{&%;Z5XlKW-#n6Es$@eQe6Y5OR}DfbPZXU5FM{)VRREMbi3 z$yEfs&BMyRu*=#y764QqRF@7s<#rxqK04`+GkVTfOc-`FHYEAN!aN)M@N#p0- zAv^stL73s0P)|d*Ci28bZrp~wptSXHXR~W zrKfbNfnpvZ0>Q)xvHOu{2~)F+yKlXdR_jAgBk!uEYoI}=G^y70GQN#z)h@0Hf%VrA z!og7QS76kHh7t75+Yg<52s#D}8?LDn1GJo$v8sADFX)^%>fTo)I8U%me&+ndcR*GZ zkEcT7A?bFb_VsJTMGtRp**Tj09w`^)su|~1hks%M{k@?fTq>J1Vw+rO1paSR~06*mnsH*>VB4m+U#meL*8_O7r0bb z^(GfL=UnZXcdICKU2&_^{n|^r_y~uo^zB239@NkVbqjRVI+;Z%{FFxiu>`LAphNY> zIKv<{3BmjUS4s)t^!4r}$BzR{J@s#7$HE#Ow;xW7P1D`C-Z2{pM<@)XooCdeU7eBD zk2tJ62AmG5aMv(-fP|d~y()Ffq3kReoan(K9}9K{7W*z;2a!ZLi;C}nGxN({_>#$u z;bVwqOvd*q8{x_&l5o<)-%)hd>1cj=UK)6quEm8)OwjX3Pmu7Mo_z;~;?-9(bE~>7 zyD}ADI+NRE992&f71Zal8U~QwJYkfi6F$5?y!a^R^{UEC&Ct_Gt|7T&QouG(+;e%s z8ls-2bpJylhX8N4_CZ8)1VfcJ)0BL$|c4Ogz;TvGif6)x3Td{wNzHNRv$9sq3Z1`IufIcq9XU5K)m+oGvz><5D zv*5I2pg42FzxVhj^Bv!>2sNtwbU@^h-EgtG>L9E&+;f{EN-TctlN+W#e~r@gKJaHS zT-)QvALL&%@}CQU3oS znftqA3@?$UTzI9vu~rTJyMpmwLb|D-pcm&;lPdZO|5@euFKriGsTFJy2mg4e720s| rKOX4+|MvesYySxP{vSk3*E1qHAiLjHjGzJm{!o_Jcv~rJ8uGsYgHd0n literal 0 HcmV?d00001 diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 6f0bb1afa71..6bfa3f90bc1 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,19 +1,6 @@ + AArch -AnyCallable -autoreload -autoreloading -CPython -Fargate -Firehose -Gunicorn -HTTPPropagator -INfo -IPv -MySQL -OpenTracing -Runtimes -RuntimeErrors -SpanContext +agentless aiobotocore aiohttp aiomysql @@ -21,22 +8,26 @@ aiopg aioredis algolia algoliasearch -agentless analytics +AnyCallable api app appsec +AppSec aredis args ascii asgi asm +assertIn async asyncio asyncpg attrs autodetected autopatching +autoreload +autoreloading aws backend backends @@ -45,15 +36,18 @@ backported backporting bdd bikeshedding +Blowfish booleans boto botocore -CGroup cassandra +cattrs +CGroup cgroups cherrypy ciapp client_ip +CMake codepath collect committer @@ -64,15 +58,20 @@ contextvar contextvars coroutine coroutines +CPU +CPython +CUPTI Cython datadog datadoghq +dataset datastore datastores dbapi ddtrace deallocating deprecations +DES deserializing django docstring @@ -80,60 +79,75 @@ doctest dogpile dogpile.cache dogstatsd -dunder dsn +dunder elasticsearch elasticsearch1 elasticsearch7 embeddings +Enablement enqueue +enqueuer entrypoint entrypoints env -enqueuer eol eventbridge exec +Fargate fastapi +Firehose formatter -gRPC generativeai gevent -graphql +Gitlab +GPU graphene +graphql greenlet greenlets grpc +gRPC gunicorn +Gunicorn hostname hostnames +hotspot http httplib +HTTPPropagator https httpx -iPython iast +IAST +importlib +INfo ini InitContainer initializer integration integrations ip +IPv +iPython iterable -JSON jinja js +JSON kafka kinesis +Kinesis kombu kubernetes kwarg kwargs -LLM langchain langchain_community +libdatadog +libddwaf lifecycle linters +LLM lockfiles logbook loguru @@ -150,30 +164,37 @@ middlewares misencoded moderations mongoengine +msgpack multiline multiprocess multithreaded mypy mysql +MySQL mysqlclient mysqldb -msgpack +# tests/contrib/openai/test_openai_v1.py +Nam namespace NeedsAppKey +NSight obfuscator +ObjectProxy +oce openai opensearch opentelemetry opentracer opentracing +OpenTracing otel -ObjectProxy packfile packfiles parameterized parsers patcher perf +Perfetto pid plugin posix @@ -183,6 +204,7 @@ preconfigured prepend prepended profiler +programmatically protobuf proxying psutil @@ -199,28 +221,34 @@ pyodbc pyston pytest pytest-bdd +PyTorch quickstart ratelimit redis rediscluster renderer renderers -resolvers repo +resolvers respawn riotfile -rq +RLock rowcount +rq runnable runtime -runtimes runtime-id -RLock +RuntimeErrors +runtimes +Runtimes sanic screenshots serializable +serverless +Serverless sha sns +SpanContext sql sqlalchemy sqlite @@ -239,23 +267,27 @@ subdomains submodule submodules substring +suitespec +TensorBoard testagent TestCase testrunner +Timeseries timestamp tokenizer tracecontext tracestate tweens -uWSGI +# docs/configuration.rst +uest unbuffered unicode uninstrumented unittest unix +unobfuscated unpatch unpatched -unobfuscated unregister unshallow unvendored @@ -264,6 +296,7 @@ url urls username uvicorn +uWSGI vendored versioned vertexai @@ -278,26 +311,3 @@ Wrapt wsgi xfail yaaredis -Kinesis -AppSec -libddwaf -Serverless -serverless -cattrs -IAST -programmatically -DES -Blowfish -Gitlab -Enablement -hotspot -CMake -libdatadog -importlib -oce -assertIn -# tests/contrib/openai/test_openai_v1.py -Nam -# docs/configuration.rst -uest -suitespec diff --git a/hatch.toml b/hatch.toml index 21610b6c776..f3a3c2cee36 100644 --- a/hatch.toml +++ b/hatch.toml @@ -368,6 +368,34 @@ python = ["3.10", "3.11", "3.12"] +## pytorch profiling test + +[envs.profiling_pytorch] +dependencies = [ + "pytest", + "pytest-cov", + "requests", + "hypothesis", + "torch>=1.8.1", + "torchvision", + "lz4", +] + +[envs.profiling_pytorch.env-vars] +DD_PROFILING_ENABLED = "true" +DD_PROFILING_PYTORCH_ENABLED = "true" +CMAKE_BUILD_PARALLEL_LEVEL = "12" + +[envs.profiling_pytorch.scripts] +test = [ + "uname -a", + "pip freeze", + "python -m pytest tests/profiling_v2/test_pytorch.py -vvv --capture=tee-sys", +] + +[[envs.profiling_pytorch.matrix]] +python = ["3.12"] + ## Unit Tests [envs.ddtrace_unit_tests] diff --git a/releasenotes/notes/profiling-add-pytorch-integration-0683123b7bb83f99.yaml b/releasenotes/notes/profiling-add-pytorch-integration-0683123b7bb83f99.yaml new file mode 100644 index 00000000000..891e039a204 --- /dev/null +++ b/releasenotes/notes/profiling-add-pytorch-integration-0683123b7bb83f99.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + profiling: Adds an experimental integration with the PyTorch profiler which can be enabled + by setting ``DD_PROFILING_PYTORCH_ENABLED=true``. This feature instruments the PyTorch + profiler API (https://pytorch.org/docs/stable/_modules/torch/profiler/profiler.html) + so that GPU profiling data can be sent to Datadog for visualization. + This feature supports torch version >= 1.8.1. diff --git a/tests/profiling_v2/simple_program_pytorch_gpu.py b/tests/profiling_v2/simple_program_pytorch_gpu.py new file mode 100644 index 00000000000..8d846c52de4 --- /dev/null +++ b/tests/profiling_v2/simple_program_pytorch_gpu.py @@ -0,0 +1,42 @@ +import torch +import torch.nn +import torch.optim +from torch.profiler import ProfilerActivity +import torch.utils.data +import torchvision.datasets +import torchvision.models +from torchvision.models import ResNet18_Weights +from torchvision.models import resnet18 +import torchvision.transforms as T + + +def cifar(): + transform = T.Compose([T.Resize(224), T.ToTensor(), T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) + train_set = torchvision.datasets.CIFAR10(root="./data", train=True, download=True, transform=transform) + train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True) + device = torch.device("cuda") + model = resnet18(weights=ResNet18_Weights.DEFAULT).cuda() + criterion = torch.nn.CrossEntropyLoss() + optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) + model.train() + + def train(data): + inputs, labels = data[0].to(device=device), data[1].to(device=device) + outputs = model(inputs) + loss = criterion(outputs, labels) + optimizer.zero_grad() + loss.backward() + optimizer.step() + + with torch.profiler.profile( + activities=[ProfilerActivity.CUDA], + ): + for step, batch_data in enumerate(train_loader): + print("step #%d" % step) + if step >= (1 + 1 + 3) * 2: + break + train(batch_data) + + +if __name__ == "__main__": + cifar() diff --git a/tests/profiling_v2/test_pytorch.py b/tests/profiling_v2/test_pytorch.py new file mode 100644 index 00000000000..e50d5b46d55 --- /dev/null +++ b/tests/profiling_v2/test_pytorch.py @@ -0,0 +1,44 @@ +import os +import sys + +import pytest + +from tests.profiling.collector import pprof_utils +from tests.utils import call_program + + +@pytest.mark.skipif(not os.getenv("DD_PROFILING_PYTORCH_ENABLED", False), reason="Not testing pytorch GPU") +def test_call_script_pytorch_gpu(tmp_path, monkeypatch): + filename = str(tmp_path / "pprof") + monkeypatch.setenv("DD_PROFILING_OUTPUT_PPROF", filename) + monkeypatch.setenv("DD_PROFILING_ENABLED", "1") + monkeypatch.setenv("DD_PROFILING_PYTORCH_ENABLED", "1") + stdout, stderr, exitcode, pid = call_program( + "ddtrace-run", sys.executable, os.path.join(os.path.dirname(__file__), "simple_program_pytorch_gpu.py") + ) + assert exitcode == 0, f"Profiler exited with code {exitcode}. Stderr: {stderr}" + + profile = pprof_utils.parse_profile(filename) + samples = pprof_utils.get_samples_with_value_type(profile, "gpu-time") + assert len(samples) > 0 + print("number of gpu time samples: ", len(samples)) + print("first sample: ", samples[0]) + + expected_sample = pprof_utils.StackEvent( + locations=[ + pprof_utils.StackLocation( + function_name="Memset (Device)", + filename="unknown-file", + line_no=0, + ), + pprof_utils.StackLocation( + function_name="PYTORCH_DeviceType.CUDA", + filename="unknown-file", + line_no=0, + ), + ], + ) + pprof_utils.assert_profile_has_sample(profile, samples=samples, expected_sample=expected_sample) + + gpu_device_label_samples = pprof_utils.get_samples_with_label_key(profile, "gpu device name") + assert len(gpu_device_label_samples) > 0 From fa4aa3deebd3e5260f64e82e409a2574bccc456c Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Mon, 16 Dec 2024 08:44:47 +0100 Subject: [PATCH 306/372] chore(iast): attach to root span if running inside pytest (#11604) Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/__init__.py | 22 ++++ ddtrace/appsec/_iast/_iast_request_context.py | 80 ++++++++----- ddtrace/appsec/_iast/_pytest_plugin.py | 110 ++++++++++++++++++ ddtrace/appsec/_iast/reporter.py | 68 +++++++++++ ddtrace/appsec/iast/__init__.py | 1 + ddtrace/contrib/pytest/_plugin_v2.py | 7 ++ ddtrace/contrib/pytest/plugin.py | 14 +++ tests/appsec/iast/aspects/test_add_aspect.py | 5 +- tests/appsec/iast/conftest.py | 4 +- .../taint_tracking/test_native_taint_range.py | 5 +- .../taint_tracking/test_taint_tracking.py | 4 +- .../contrib/django/test_django_appsec_iast.py | 4 +- .../fastapi/test_fastapi_appsec_iast.py | 5 +- tests/contrib/flask/app.py | 7 ++ ...st_appsec_flask_pytest_iast_no_snapshot.py | 59 ++++++++++ tests/contrib/flask/test_flask_appsec_iast.py | 3 +- tests/contrib/flask/test_flask_pytest_iast.py | 28 +++++ 17 files changed, 391 insertions(+), 35 deletions(-) create mode 100644 ddtrace/appsec/_iast/_pytest_plugin.py create mode 100644 tests/contrib/flask/test_appsec_flask_pytest_iast_no_snapshot.py create mode 100644 tests/contrib/flask/test_flask_pytest_iast.py diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index 3e4b04a0b6a..fe488c87e46 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -29,10 +29,12 @@ def wrapped_function(wrapped, instance, args, kwargs): """ # noqa: RST201, RST213, RST210 import inspect +import os import sys from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog +from ddtrace.settings.asm import config as asm_config from ._overhead_control_engine import OverheadControl from ._utils import _is_iast_enabled @@ -91,6 +93,26 @@ def enable_iast_propagation(): _iast_propagation_enabled = True +def _iast_pytest_activation(): + global _iast_propagation_enabled + global oce + if _iast_propagation_enabled: + return + os.environ["DD_IAST_ENABLED"] = os.environ.get("DD_IAST_ENABLED") or "1" + os.environ["_DD_IAST_USE_ROOT_SPAN"] = os.environ.get("_DD_IAST_USE_ROOT_SPAN") or "true" + os.environ["DD_IAST_REQUEST_SAMPLING"] = os.environ.get("DD_IAST_REQUEST_SAMPLING") or "100.0" + os.environ["_DD_APPSEC_DEDUPLICATION_ENABLED"] = os.environ.get("_DD_APPSEC_DEDUPLICATION_ENABLED") or "false" + os.environ["DD_IAST_VULNERABILITIES_PER_REQUEST"] = os.environ.get("DD_IAST_VULNERABILITIES_PER_REQUEST") or "1000" + os.environ["DD_IAST_MAX_CONCURRENT_REQUESTS"] = os.environ.get("DD_IAST_MAX_CONCURRENT_REQUESTS") or "1000" + + asm_config._iast_request_sampling = 100.0 + asm_config._deduplication_enabled = False + asm_config._iast_max_vulnerabilities_per_requests = 1000 + asm_config._iast_max_concurrent_requests = 1000 + enable_iast_propagation() + oce.reconfigure() + + def disable_iast_propagation(): """Remove IAST AST patching from the ModuleWatchdog. Only for testing proposes""" # DEV: These imports are here to avoid _ast.ast_patching import in the top level diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index f49d2bc59bd..b711ae61195 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -1,3 +1,4 @@ +import os import sys from typing import Dict from typing import Optional @@ -21,6 +22,7 @@ from ddtrace.constants import ORIGIN_KEY from ddtrace.internal import core from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.formats import asbool log = get_logger(__name__) @@ -109,39 +111,61 @@ def is_iast_request_enabled(): return False +def _move_iast_data_to_root_span(): + return asbool(os.getenv("_DD_IAST_USE_ROOT_SPAN")) + + +def _create_and_attach_iast_report_to_span(req_span: Span, existing_data: Optional[str], merge: bool = False): + report_data: Optional[IastSpanReporter] = get_iast_reporter() + if merge and existing_data is not None and report_data is not None: + previous_data = IastSpanReporter() + previous_data._from_json(existing_data) + + report_data._merge(previous_data) + + if report_data is not None: + report_data.build_and_scrub_value_parts() + req_span.set_tag_str(IAST.JSON, report_data._to_str()) + _set_metric_iast_request_tainted() + _set_span_tag_iast_request_tainted(req_span) + _set_span_tag_iast_executed_sink(req_span) + + set_iast_request_enabled(False) + end_iast_context(req_span) + + if req_span.get_tag(ORIGIN_KEY) is None: + req_span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + + oce.release_request() + + def _iast_end_request(ctx=None, span=None, *args, **kwargs): try: - if span: - req_span = span + move_to_root = _move_iast_data_to_root_span() + if move_to_root: + req_span = core.get_root_span() else: - req_span = ctx.get_item("req_span") + if span: + req_span = span + else: + req_span = ctx.get_item("req_span") if _is_iast_enabled(): - exist_data = req_span.get_tag(IAST.JSON) - if exist_data is None and req_span.get_metric(IAST.ENABLED) is None: - if not is_iast_request_enabled(): - req_span.set_metric(IAST.ENABLED, 0.0) - end_iast_context(req_span) - oce.release_request() - return - - req_span.set_metric(IAST.ENABLED, 1.0) - report_data: Optional[IastSpanReporter] = get_iast_reporter() - - if report_data: - report_data.build_and_scrub_value_parts() - req_span.set_tag_str(IAST.JSON, report_data._to_str()) - _set_metric_iast_request_tainted() - _set_span_tag_iast_request_tainted(req_span) - _set_span_tag_iast_executed_sink(req_span) - - set_iast_request_enabled(False) - end_iast_context(req_span) - - if req_span.get_tag(ORIGIN_KEY) is None: - req_span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) - - oce.release_request() + existing_data = req_span.get_tag(IAST.JSON) + if existing_data is None: + if req_span.get_metric(IAST.ENABLED) is None: + if not is_iast_request_enabled(): + req_span.set_metric(IAST.ENABLED, 0.0) + end_iast_context(req_span) + oce.release_request() + return + + req_span.set_metric(IAST.ENABLED, 1.0) + _create_and_attach_iast_report_to_span(req_span, existing_data, merge=False) + + elif move_to_root: + # Data exists from a previous request, we will merge both reports + _create_and_attach_iast_report_to_span(req_span, existing_data, merge=True) except Exception: log.debug("[IAST] Error finishing IAST context", exc_info=True) diff --git a/ddtrace/appsec/_iast/_pytest_plugin.py b/ddtrace/appsec/_iast/_pytest_plugin.py new file mode 100644 index 00000000000..672acc4a031 --- /dev/null +++ b/ddtrace/appsec/_iast/_pytest_plugin.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +import dataclasses +import json +from typing import List + +from ddtrace.appsec._constants import IAST +from ddtrace.appsec._iast._utils import _is_iast_enabled +from ddtrace.appsec._iast.reporter import Vulnerability +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +@dataclasses.dataclass(unsafe_hash=True) +class VulnerabilityFoundInTest(Vulnerability): + test: str + + +try: + import pytest + + @pytest.fixture(autouse=_is_iast_enabled()) + def ddtrace_iast(request, ddspan): + """ + Extract the vulnerabilities discovered in tests. + Optionally output the test as failed if vulnerabilities are found. + """ + yield + data = ddspan.get_tag(IAST.JSON) + if not data: + return + + json_data = json.loads(data) + + if json_data["vulnerabilities"]: + for vuln in json_data["vulnerabilities"]: + vuln_data.append( + VulnerabilityFoundInTest( + test=request.node.nodeid, + type=vuln["type"], + evidence=vuln["evidence"], + location=vuln["location"], + ) + ) + + if request.config.getoption("ddtrace-iast-fail-tests"): + vulns = ", ".join([vuln["type"] for vuln in json_data["vulnerabilities"]]) + pytest.fail(f"There are vulnerabilities in the code: {vulns}") + +except ImportError: + log.debug("pytest not imported") + + +vuln_data: List[VulnerabilityFoundInTest] = [] + + +def extract_code_snippet(filepath, line_number, context=3): + """Extracts code snippet around the given line number.""" + try: + with open(filepath, "r") as file: + lines = file.readlines() + start = max(0, line_number - context - 1) + end = min(len(lines), line_number + context) + code = lines[start:end] + return code, start # Return lines and starting line number + except Exception: + log.debug("Error reading file %s", filepath, exc_info=True) + return "", 0 + + +def print_iast_report(terminalreporter): + if not _is_iast_enabled(): + return + + if not vuln_data: + terminalreporter.write_sep("=", "Datadog Code Security Report", purple=True, bold=True) + terminalreporter.write_line("No vulnerabilities found.") + return + + terminalreporter.write_sep("=", "Datadog Code Security Report", purple=True, bold=True) + + for entry in vuln_data: + terminalreporter.write_line(f"Test: {entry.test}", bold=True) + high_severity = entry.type.endswith("INJECTION") + terminalreporter.write_line( + f"Vulnerability: {entry.type}", + # TODO(@gnufede): Add remediation links, where remediation is a dict with the vulnerability as key + # f" - \033]8;;{remediation[entry.type]}\033\\Remediation\033]8;;\033\\ \n", + bold=True, + red=high_severity, + yellow=not high_severity, + ) + terminalreporter.write_line(f"Location: {entry.location['path']}:{entry.location['line']}") + code_snippet, start_line = extract_code_snippet(entry.location["path"], entry.location["line"]) + + if code_snippet: + terminalreporter.write_line("Code:") + + if start_line is not None: + for i, line in enumerate(code_snippet, start=start_line + 1): + if i == entry.location["line"]: + terminalreporter.write(f"{i:4d}: {line}", bold=True, purple=True) + else: + terminalreporter.write(f"{i:4d}: {line}") + else: + # If there's an error extracting the code snippet + terminalreporter.write_line(code_snippet[0], bold=True) + + terminalreporter.write_sep("=") diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index 249d8e21278..c7004909cc9 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -121,6 +121,74 @@ def __hash__(self) -> int: """ return reduce(operator.xor, (hash(obj) for obj in set(self.sources) | self.vulnerabilities)) + def _merge(self, other: "IastSpanReporter") -> None: + """ + Merges the current IAST span reporter with another IAST span reporter. + + Args: + - other (IastSpanReporter): IAST span reporter to merge. + """ + len_previous_sources = len(self.sources) + self.sources = self.sources + other.sources + self._update_vulnerabilities(other, len_previous_sources) + + def _update_vulnerabilities(self, other: "IastSpanReporter", offset: int): + for vuln in other.vulnerabilities: + if ( + hasattr(vuln, "evidence") + and hasattr(vuln.evidence, "valueParts") + and vuln.evidence.valueParts is not None + ): + for part in vuln.evidence.valueParts: + if "source" in part: + part["source"] = part["source"] + offset + self.vulnerabilities.add(vuln) + + def _from_json(self, json_str: str): + """ + Initializes the IAST span reporter from a JSON string. + + Args: + - json_str (str): JSON string. + """ + from ._taint_tracking import str_to_origin + + data = json.loads(json_str) + self.sources = [] + for i in data["sources"]: + source = Source( + origin=str_to_origin(i["origin"]), + name=i["name"], + ) + if "value" in i: + source.value = i["value"] + if "redacted" in i: + source.redacted = i["redacted"] + if "pattern" in i: + source.pattern = i["pattern"] + self.sources.append(source) + + self.vulnerabilities = set() + for i in data["vulnerabilities"]: + evidence = Evidence() + if "ranges" in i["evidence"]: + evidence._ranges = i["evidence"]["ranges"] + if "value" in i["evidence"]: + evidence.value = i["evidence"]["value"] + if "valueParts" in i["evidence"]: + evidence.valueParts = i["evidence"]["valueParts"] + if "dialect" in i["evidence"]: + evidence.dialect = i["evidence"]["dialect"] + self.vulnerabilities.add( + Vulnerability( + type=i["type"], + evidence=evidence, + location=Location( + spanId=i["location"]["spanId"], path=i["location"]["path"], line=i["location"]["line"] + ), + ) + ) + def _to_dict(self): return { "sources": [i._to_dict() for i in self.sources], diff --git a/ddtrace/appsec/iast/__init__.py b/ddtrace/appsec/iast/__init__.py index c72c2be9167..ece53d092cb 100644 --- a/ddtrace/appsec/iast/__init__.py +++ b/ddtrace/appsec/iast/__init__.py @@ -1,2 +1,3 @@ +from ddtrace.appsec._iast import _iast_pytest_activation # noqa: F401 from ddtrace.appsec._iast import ddtrace_iast_flask_patch # noqa: F401 from ddtrace.appsec._iast import enable_iast_propagation # noqa: F401 diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index d3825578d7a..e9420f62527 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -533,6 +533,13 @@ def _pytest_terminal_summary_post_yield(terminalreporter, failed_reports_initial @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_terminal_summary(terminalreporter, exitstatus, config): """Report flaky or failed tests""" + try: + from ddtrace.appsec._iast._pytest_plugin import print_iast_report + + print_iast_report(terminalreporter) + except Exception: # noqa: E722 + log.debug("Encountered error during code security summary", exc_info=True) + if not is_test_visibility_enabled(): yield return diff --git a/ddtrace/contrib/pytest/plugin.py b/ddtrace/contrib/pytest/plugin.py index a8da8c3a5ca..a09a81be49a 100644 --- a/ddtrace/contrib/pytest/plugin.py +++ b/ddtrace/contrib/pytest/plugin.py @@ -15,6 +15,8 @@ import pytest +from ddtrace.appsec._iast._pytest_plugin import ddtrace_iast # noqa:F401 +from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 from ddtrace.contrib.pytest._utils import _extract_span from ddtrace.contrib.pytest._utils import _pytest_version_supports_itr @@ -67,10 +69,22 @@ def pytest_addoption(parser): help=DDTRACE_INCLUDE_CLASS_HELP_MSG, ) + group._addoption( + "--ddtrace-iast-fail-tests", + action="store_true", + dest="ddtrace-iast-fail-tests", + default=False, + help=DDTRACE_INCLUDE_CLASS_HELP_MSG, + ) + parser.addini("ddtrace", DDTRACE_HELP_MSG, type="bool") parser.addini("no-ddtrace", DDTRACE_HELP_MSG, type="bool") parser.addini("ddtrace-patch-all", PATCH_ALL_HELP_MSG, type="bool") parser.addini("ddtrace-include-class-name", DDTRACE_INCLUDE_CLASS_HELP_MSG, type="bool") + if _is_iast_enabled(): + from ddtrace.appsec._iast import _iast_pytest_activation + + _iast_pytest_activation() # Version-specific pytest hooks diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index db8f9b212c8..f9f86a4413c 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -15,6 +15,7 @@ from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce +from tests.utils import override_env from tests.utils import override_global_config @@ -319,7 +320,9 @@ def test_propagate_ranges_with_no_context(caplog): ) reset_context() - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): result_2 = add_aspect(result, "another_string") create_context() diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index a277e912829..3daa3611f51 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -142,7 +142,9 @@ def check_native_code_exception_in_each_python_aspect_test(request, caplog): if "skip_iast_check_logs" in request.keywords: yield else: - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): yield log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range.py b/tests/appsec/iast/taint_tracking/test_native_taint_range.py index d1683b5ffb4..00079d7772b 100644 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range.py +++ b/tests/appsec/iast/taint_tracking/test_native_taint_range.py @@ -32,6 +32,7 @@ from ddtrace.appsec._iast._taint_tracking.aspects import format_aspect from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect from tests.appsec.iast.conftest import IAST_VALID_LOG +from tests.utils import override_env from tests.utils import override_global_config @@ -499,7 +500,9 @@ def test_race_conditions_reset_contexts_threads(caplog, telemetry_writer): """we want to validate context is working correctly among multiple request and no race condition creating and destroying contexts """ - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): pool = ThreadPool(processes=3) results_async = [pool.apply_async(reset_contexts_loop) for _ in range(70)] _ = [res.get() for res in results_async] diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index 90d9b0c064a..ac3d009633f 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -47,7 +47,9 @@ def test_taint_object_with_no_context_should_be_noop(): @pytest.mark.skip_iast_check_logs def test_propagate_ranges_with_no_context(caplog): reset_context() - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): string_input = taint_pyobject( pyobject="abcde", source_name="abcde", source_value="abcde", source_origin=OriginType.PARAMETER ) diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index 89495dcac80..efe0fa9acd0 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -41,7 +41,9 @@ def check_native_code_exception_in_each_django_test(request, caplog, telemetry_w yield else: caplog.set_level(logging.DEBUG) - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): yield log_messages = [record.message for record in caplog.get_records("call")] diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 9688c7d06b7..7f1a140ffc2 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -24,6 +24,7 @@ from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.utils import override_env from tests.utils import override_global_config @@ -57,7 +58,9 @@ def check_native_code_exception_in_each_fastapi_test(request, caplog, telemetry_ yield else: caplog.set_level(logging.DEBUG) - with override_global_config(dict(_iast_debug=True)), caplog.at_level(logging.DEBUG): + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( + dict(_iast_debug=True) + ), caplog.at_level(logging.DEBUG): yield log_messages = [record.msg for record in caplog.get_records("call")] diff --git a/tests/contrib/flask/app.py b/tests/contrib/flask/app.py index fbd06dd6990..82059ce0eaa 100644 --- a/tests/contrib/flask/app.py +++ b/tests/contrib/flask/app.py @@ -1,3 +1,4 @@ +import hashlib import os import subprocess import sys @@ -100,3 +101,9 @@ def run_subcommunicatenoshell(): subp.wait() ret = subp.returncode return str(ret), 200 + + +@app.route("/md5sum") +def md5sum(): + data = request.args.get("q").encode() + return hashlib.md5(data).hexdigest() diff --git a/tests/contrib/flask/test_appsec_flask_pytest_iast_no_snapshot.py b/tests/contrib/flask/test_appsec_flask_pytest_iast_no_snapshot.py new file mode 100644 index 00000000000..801cffa4b8a --- /dev/null +++ b/tests/contrib/flask/test_appsec_flask_pytest_iast_no_snapshot.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import time + +import pytest + + +@pytest.mark.parametrize("iast_enabled", ["true", "false"]) +@pytest.mark.parametrize("iast_request_sampling", ["100.0", "0.0"]) +@pytest.mark.parametrize("pytest_use_new_plugin", ["true", "false"]) +def test_flask_pytest_iast(iast_enabled, iast_request_sampling, pytest_use_new_plugin): + from tests.utils import _build_env + + env = _build_env() + env.update( + { + # Avoid noisy database spans being output on app startup/teardown. + "DD_TRACE_SQLITE3_ENABLED": "0", + "DD_TRACE_SQLITE_ENABLED": "0", + "DD_IAST_ENABLED": iast_enabled, + "DD_TRACE_DEBUG": "true", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": pytest_use_new_plugin, + "DD_IAST_REQUEST_SAMPLING": iast_request_sampling, + # "DD_API_KEY": "invalidapikey", + # "DD_CIVISIBILITY_AGENTLESS_ENABLED": "1", + } + ) + proc = subprocess.Popen( + "pytest --ddtrace --ddtrace-patch-all --no-cov tests/contrib/flask/test_flask_pytest_iast.py".split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + env=env, + preexec_fn=os.setsid, + cwd=str(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))), + ) + try: + time.sleep(0.2) + finally: + proc.wait() + # DEV uncomment this line if you need more info locally + # stdout = proc.stdout.read() + + stderr = proc.stderr.read() + split_stderr = stderr.decode("utf-8").split("\n") + + found = False + for line in split_stderr: + if "WEAK_HASH" in line: + assert line.startswith("finishing span name='pytest.test'") + found = True + break + + if iast_enabled == "true" and iast_request_sampling == "100": + assert found + else: + assert not found diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index 020cbe27a98..f1bed61cb9d 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -19,6 +19,7 @@ from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash from tests.contrib.flask import BaseFlaskTestCase +from tests.utils import override_env from tests.utils import override_global_config @@ -35,7 +36,7 @@ def inject_fixtures(self, caplog, telemetry_writer): # noqa: F811 self._caplog = caplog def setUp(self): - with override_global_config( + with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( dict( _iast_enabled=True, _deduplication_enabled=False, diff --git a/tests/contrib/flask/test_flask_pytest_iast.py b/tests/contrib/flask/test_flask_pytest_iast.py new file mode 100644 index 00000000000..fcacf6b36c9 --- /dev/null +++ b/tests/contrib/flask/test_flask_pytest_iast.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +""" +This test suite is actually used as fixture in end-to-end test +for pytest IAST integration. +""" + +import urllib.parse + +import pytest + +from .app import app as real_app + + +@pytest.fixture() +def app(): + return real_app + + +@pytest.fixture() +def client(app): + return app.test_client() + + +def test_md5_request(client): + data = b"foobar" + urlencoded_data = urllib.parse.urlencode({"q": data}) + response = client.get("/md5sum?%s" % urlencoded_data) + assert response.status_code == 200 From fb82c64f516048e7146ce6a5bcd8961a6cded29a Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 16 Dec 2024 11:10:18 +0000 Subject: [PATCH 307/372] ci: pass correct suite names in CircleCI gen script (#11668) We make sure to pass the correct suite names where required so that the right jobs can be included/excluded. The current behaviour failed to resolve paths for suite names, causing all the Circle CI jobs to run. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/gen_circleci_config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/gen_circleci_config.py b/scripts/gen_circleci_config.py index 0c7c8344e58..7225ea38d22 100644 --- a/scripts/gen_circleci_config.py +++ b/scripts/gen_circleci_config.py @@ -17,10 +17,9 @@ def gen_required_suites(template: dict) -> None: required_suites = template["requires_tests"]["requires"] = [] for_each_testrun_needed( suites=sorted( - set(n.rpartition("::")[-1] for n, s in get_suites().items() if not s.get("skip", False)) - & set(template["jobs"].keys()) + set(n for n, s in get_suites().items() if not s.get("skip", False)) & set(template["jobs"].keys()) ), - action=lambda suite: required_suites.append(suite), + action=lambda suite: required_suites.append(suite.rpartition("::")[-1]), git_selections=extract_git_commit_selections(os.getenv("GIT_COMMIT_DESC", "")), ) From cc03ddc643360c272cf1ca8fd541760af170fca0 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 16 Dec 2024 11:18:04 +0000 Subject: [PATCH 308/372] chore(di): accurate function line mapping (#11607) We make the line-to-function mapping more accurate by capturing the code objects exported by a module before the module is executed. This gives us the full code object content of the model, which matches the source more closely than the executed module. As a result, we are able to get hold of decorated functions before they "disappear" inside an arbitrary data structure of a custom decorator. ## Performance Considerations The extra accuracy comes with memory and computing costs. The captured code objects have to be stored on the module. We expect the memory cost to be < 1% the overall memory footprint of the target application. When a function location is looked up, the code object needs to be resolved to a function. We query the GC to find what we are looking for. This is generally an expensive operation, so we perform it on demand, when an instrumentation needs to be applied, instead of looking up every code object after a module import. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 4 + ddtrace/debugging/_function/discovery.py | 155 ++++++++++++++++++--- ddtrace/internal/utils/inspection.py | 10 ++ tests/debugging/function/test_discovery.py | 26 +++- tests/debugging/test_debugger.py | 104 ++++++-------- tests/submod/custom_decorated_stuff.py | 17 +++ 6 files changed, 235 insertions(+), 81 deletions(-) create mode 100644 tests/submod/custom_decorated_stuff.py diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 65b9ecfec5e..7d4a283b26d 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -6,6 +6,7 @@ from pathlib import Path import sys import threading +from types import CodeType from types import FunctionType from types import ModuleType from types import TracebackType @@ -73,6 +74,9 @@ class DebuggerError(Exception): class DebuggerModuleWatchdog(ModuleWatchdog): _locations: Set[str] = set() + def transform(self, code: CodeType, module: ModuleType) -> CodeType: + return FunctionDiscovery.transformer(code, module) + @classmethod def register_origin_hook(cls, origin: Path, hook: ModuleHookType) -> None: if origin in cls._locations: diff --git a/ddtrace/debugging/_function/discovery.py b/ddtrace/debugging/_function/discovery.py index 9cabb4b3a04..e7d37246f5f 100644 --- a/ddtrace/debugging/_function/discovery.py +++ b/ddtrace/debugging/_function/discovery.py @@ -4,6 +4,7 @@ from wrapt import FunctionWrapper +from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.utils.inspection import undecorated @@ -12,6 +13,7 @@ except ImportError: from typing_extensions import Protocol # type: ignore[assignment] +from types import CodeType from types import FunctionType from types import ModuleType from typing import Any @@ -27,6 +29,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.module import origin from ddtrace.internal.safety import _isinstance +from ddtrace.internal.utils.inspection import collect_code_objects from ddtrace.internal.utils.inspection import linenos @@ -49,6 +52,8 @@ class FullyNamed(Protocol): class FullyNamedFunction(FullyNamed): """A fully named function object.""" + __qualname__: str + def __call__(self, *args, **kwargs): pass @@ -119,7 +124,48 @@ def _local_name(name: str, f: FunctionType) -> str: return func_name -def _collect_functions(module: ModuleType) -> Dict[str, FullyNamedFunction]: +class _FunctionCodePair: + """Function-Code Pair + + This class allows us to resolve a code object to a function object by + querying the GC on-demand. + """ + + __slots__ = ("function", "code") + + def __init__(self, code: Optional[CodeType] = None, function: Optional[FunctionType] = None) -> None: + if code is not None and function is not None and function.__code__ is not code: + raise ValueError("Function and code objects do not match") + + self.function = function + self.code = function.__code__ if function is not None else code + + def resolve(self) -> FullyNamedFunction: + import gc + + if self.function is not None: + return cast(FullyNamedFunction, self.function) + + code = self.code + functions = [_ for _ in gc.get_referrers(code) if isinstance(_, FunctionType) and _.__code__ is code] + n = len(functions) + if n == 0: + msg = f"Cannot resolve code object to function: {code}" + raise ValueError(msg) + if n > 1: + # This can happen for functions that are created at runtime rather + # than compile time. We do not support this case deliberately for + # now. + msg = f"Multiple functions found for code object {code}" + raise ValueError(msg) + + f = cast(FullyNamedFunction, functions[0]) + f.__fullname__ = f"{f.__module__}.{f.__qualname__}" + + return f + + +def _collect_functions(module: ModuleType) -> Dict[str, _FunctionCodePair]: """Collect functions from a given module. All the collected functions are augmented with a ``__fullname__`` attribute @@ -160,7 +206,9 @@ def _collect_functions(module: ModuleType) -> Dict[str, FullyNamedFunction]: # try to retrieve any potentially decorated function so # that we don't end up returning the decorator function # instead of the original function. - functions[fullname] = undecorated(f, name, path) if name == k else o + functions[fullname] = _FunctionCodePair( + function=cast(FunctionType, undecorated(f, name, path) if name == k else o) + ) try: if f.__closure__: @@ -189,28 +237,46 @@ class FunctionDiscovery(defaultdict): def __init__(self, module: ModuleType) -> None: super().__init__(list) - self._module = module - self._fullname_index = {} - functions = _collect_functions(module) - seen_functions = set() module_path = origin(module) if module_path is None: # We are not going to collect anything because no code objects will # match the origin. return - for fname, function in functions.items(): - if ( - function not in seen_functions - and Path(cast(FunctionType, function).__code__.co_filename).resolve() == module_path - ): - # We only map line numbers for functions that actually belong to - # the module. - for lineno in linenos(cast(FunctionType, function)): - self[lineno].append(function) - self._fullname_index[fname] = function - seen_functions.add(function) + self._module = module + self._fullname_index = _collect_functions(module) + if PYTHON_VERSION_INFO < (3, 11): + self._name_index: Dict[str, List[_FunctionCodePair]] = defaultdict(list) + self._cached: Dict[int, List[FullyNamedFunction]] = {} + + # Create the line to function mapping + if hasattr(module, "__dd_code__"): + for code in module.__dd_code__: + fcp = _FunctionCodePair(code=code) + if PYTHON_VERSION_INFO >= (3, 11): + # From this version of Python we can derive the qualified + # name of the function directly from the code object. + fullname = f"{module.__name__}.{code.co_qualname}" + self._fullname_index[fullname] = fcp + else: + self._name_index[code.co_name].append(fcp) + for lineno in linenos(code): + self[lineno].append(_FunctionCodePair(code=code)) + else: + # If the module was already loaded we don't have its code object + seen_functions = set() + for _, fcp in self._fullname_index.items(): + function = fcp.resolve() + if ( + function not in seen_functions + and Path(cast(FunctionType, function).__code__.co_filename).resolve() == module_path + ): + # We only map line numbers for functions that actually belong to + # the module. + for lineno in linenos(cast(FunctionType, function)): + self[lineno].append(_FunctionCodePair(function=cast(FunctionType, function))) + seen_functions.add(function) def at_line(self, line: int) -> List[FullyNamedFunction]: """Get the functions at the given line. @@ -218,14 +284,55 @@ def at_line(self, line: int) -> List[FullyNamedFunction]: Note that, in general, there can be multiple copies of the same functions. This can happen as a result, e.g., of using decorators. """ - return self[line] + if line in self._cached: + return self._cached[line] + + if line in self: + functions = [] + for fcp in self[line]: + try: + functions.append(fcp.resolve()) + except ValueError: + pass + + if not functions: + del self[line] + else: + self._cached[line] = functions + + return functions + + return [] def by_name(self, qualname: str) -> FullyNamedFunction: """Get the function by its qualified name.""" - fullname = ".".join((self._module.__name__, qualname)) + fullname = f"{self._module.__name__}.{qualname}" try: - return self._fullname_index[fullname] + return self._fullname_index[fullname].resolve() except KeyError: + if PYTHON_VERSION_INFO < (3, 11): + # Check if any code objects whose names match the last part of + # the qualified name have a function with the same qualified + # name. + for name, fcps in self._name_index.items(): + if qualname == name or qualname.endswith(f".{name}"): + for fcp in list(fcps): + try: + f = fcp.resolve() + + # We have resolved the function so we can now + # get its full name + self._fullname_index[f"{self._module.__name__}.{f.__qualname__}"] = fcp + + # We can remove the entry from the name index + fcps.pop(0) + + # If this is the function we are looking for, + # return it + if f.__qualname__ == qualname: + return f + except ValueError: + pass raise ValueError("Function '%s' not found" % fullname) @classmethod @@ -241,4 +348,12 @@ def from_module(cls, module: ModuleType) -> "FunctionDiscovery": return module.__function_discovery__ except AttributeError: fd = module.__function_discovery__ = cls(module) # type: ignore[attr-defined] + if hasattr(module, "__dd_code__"): + # We no longer need to keep this collection around + del module.__dd_code__ return fd + + @classmethod + def transformer(cls, code: CodeType, module: ModuleType) -> CodeType: + module.__dd_code__ = collect_code_objects(code) # type: ignore[attr-defined] # type: ignore[attr-defined] + return code diff --git a/ddtrace/internal/utils/inspection.py b/ddtrace/internal/utils/inspection.py index 7f739d4bf65..bb24a3ae80d 100644 --- a/ddtrace/internal/utils/inspection.py +++ b/ddtrace/internal/utils/inspection.py @@ -5,6 +5,7 @@ from pathlib import Path from types import CodeType from types import FunctionType +from typing import Iterator from typing import Set from typing import cast @@ -112,3 +113,12 @@ def match(g): pass return f + + +def collect_code_objects(code: CodeType) -> Iterator[CodeType]: + q = deque([code]) + while q: + c = q.popleft() + for new_code in (_ for _ in c.co_consts if isinstance(_, CodeType)): + yield new_code + q.append(new_code) diff --git a/tests/debugging/function/test_discovery.py b/tests/debugging/function/test_discovery.py index 1cd977d06af..6f247bc7fd0 100644 --- a/tests/debugging/function/test_discovery.py +++ b/tests/debugging/function/test_discovery.py @@ -1,6 +1,7 @@ import pytest from ddtrace.debugging._function.discovery import FunctionDiscovery +from ddtrace.internal.module import ModuleWatchdog import tests.submod.stuff as stuff @@ -12,7 +13,7 @@ def stuff_discovery(): def test_abs_stuff(): import tests.submod.absstuff as absstuff - assert sorted(FunctionDiscovery.from_module(absstuff).keys()) == [7, 11, 16, 19] + assert set(FunctionDiscovery.from_module(absstuff).keys()) >= {7, 11, 16, 19} def test_function_discovery(stuff_discovery): @@ -106,16 +107,35 @@ def test_discovery_after_external_wrapping(stuff): def wrapper(wrapped, inst, args, kwargs): pass + original_function = stuff.Stuff.instancestuff + wrapt.wrap_function_wrapper(stuff, "Stuff.instancestuff", wrapper) assert isinstance(stuff.Stuff.instancestuff, (wrapt.BoundFunctionWrapper, wrapt.FunctionWrapper)) code = stuff.Stuff.instancestuff.__code__ - f = FunctionDiscovery(stuff)[36][0] + f, *_ = FunctionDiscovery(stuff).at_line(36) - assert isinstance(f, (wrapt.BoundFunctionWrapper, wrapt.FunctionWrapper)) + assert f is original_function or isinstance(f, (wrapt.BoundFunctionWrapper, wrapt.FunctionWrapper)), f assert f.__code__ is code def test_property_non_function_getter(stuff_discovery): with pytest.raises(ValueError): stuff_discovery.by_name("PropertyStuff.foo") + + +def test_custom_decorated_stuff(): + class DiscoveryModuleWatchdog(ModuleWatchdog): + def transform(self, code, module): + return FunctionDiscovery.transformer(code, module) + + DiscoveryModuleWatchdog.install() + + import tests.submod.custom_decorated_stuff as custom_decorated_stuff + + fd = FunctionDiscovery.from_module(custom_decorated_stuff) + + (home,) = fd.at_line(17) + assert home.__qualname__ == "home" + + DiscoveryModuleWatchdog.uninstall() diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index 2a0bdaf13d8..ed337c27f1e 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -39,8 +39,6 @@ from tests.debugging.utils import ddexpr from tests.debugging.utils import ddstrtempl from tests.internal.remoteconfig import rcm_endpoint -from tests.submod.stuff import Stuff -from tests.submod.stuff import modulestuff as imported_modulestuff from tests.utils import TracerTestCase from tests.utils import call_program @@ -71,7 +69,7 @@ def simple_debugger_test(probe, func): return snapshots -def test_debugger_line_probe_on_instance_method(): +def test_debugger_line_probe_on_instance_method(stuff): snapshots = simple_debugger_test( create_snapshot_line_probe( probe_id="probe-instance-method", @@ -79,7 +77,7 @@ def test_debugger_line_probe_on_instance_method(): line=36, condition=None, ), - lambda: Stuff().instancestuff(), + stuff.Stuff().instancestuff, ) (snapshot,) = snapshots @@ -89,15 +87,15 @@ def test_debugger_line_probe_on_instance_method(): assert snapshot["debugger"]["snapshot"]["duration"] is None -def test_debugger_line_probe_on_imported_module_function(): - lineno = min(linenos(imported_modulestuff)) +def test_debugger_line_probe_on_imported_module_function(stuff): + lineno = min(linenos(stuff.modulestuff)) snapshots = simple_debugger_test( create_snapshot_line_probe( probe_id="probe-instance-method", source_file="tests/submod/stuff.py", line=lineno, ), - lambda: imported_modulestuff(42), + lambda: stuff.modulestuff(42), ) (snapshot,) = snapshots @@ -107,7 +105,7 @@ def test_debugger_line_probe_on_imported_module_function(): @pytest.mark.parametrize( - "probe, trigger", + "probe", [ ( create_snapshot_function_probe( @@ -115,8 +113,7 @@ def test_debugger_line_probe_on_imported_module_function(): module="tests.submod.stuff", func_qname="Stuff.instancestuff", rate=1000, - ), - lambda: Stuff().instancestuff(42), + ) ), ( create_snapshot_line_probe( @@ -124,14 +121,11 @@ def test_debugger_line_probe_on_imported_module_function(): source_file="tests/submod/stuff.py", line=36, rate=1000, - ), - lambda: Stuff().instancestuff(42), + ) ), ], ) -def test_debugger_probe_new_delete(probe, trigger): - global Stuff - +def test_debugger_probe_new_delete(probe, stuff): with debugger() as d: probe_id = probe.probe_id d.add_probes(probe) @@ -139,7 +133,7 @@ def test_debugger_probe_new_delete(probe, trigger): assert probe in d._probe_registry assert _get_probe_location(probe) in d.__watchdog__._instance._locations - trigger() + stuff.Stuff().instancestuff(42) d.remove_probes(probe) @@ -148,7 +142,7 @@ def test_debugger_probe_new_delete(probe, trigger): assert _get_probe_location(probe) not in d.__watchdog__._instance._locations - trigger() + stuff.Stuff().instancestuff(42) # Unload and reload the module to ensure that the injection hook # has actually been removed. @@ -158,15 +152,15 @@ def test_debugger_probe_new_delete(probe, trigger): __import__("tests.submod.stuff") # Make Stuff refer to the reloaded class - Stuff = sys.modules["tests.submod.stuff"].Stuff + stuff.Stuff = sys.modules["tests.submod.stuff"].Stuff - trigger() + stuff.Stuff().instancestuff(42) (snapshot,) = d.uploader.wait_for_payloads() assert snapshot["debugger"]["snapshot"]["probe"]["id"] == probe_id -def test_debugger_function_probe_on_instance_method(): +def test_debugger_function_probe_on_instance_method(stuff): snapshots = simple_debugger_test( create_snapshot_function_probe( probe_id="probe-instance-method", @@ -174,7 +168,7 @@ def test_debugger_function_probe_on_instance_method(): func_qname="Stuff.instancestuff", condition=None, ), - lambda: Stuff().instancestuff(42), + lambda: stuff.Stuff().instancestuff(42), ) (snapshot,) = snapshots @@ -221,7 +215,7 @@ def test_debugger_function_probe_on_function_with_exception(): assert return_capture["throwable"]["type"] == "Exception" -def test_debugger_invalid_condition(): +def test_debugger_invalid_condition(stuff): with debugger() as d: d.add_probes( create_snapshot_line_probe( @@ -232,12 +226,12 @@ def test_debugger_invalid_condition(): ), good_probe(), ) - Stuff().instancestuff() + stuff.Stuff().instancestuff() assert all(s["debugger"]["snapshot"]["probe"]["id"] != "foo" for s in d.uploader.wait_for_payloads()) -def test_debugger_conditional_line_probe_on_instance_method(): +def test_debugger_conditional_line_probe_on_instance_method(stuff): snapshots = simple_debugger_test( create_snapshot_line_probe( probe_id="probe-instance-method", @@ -245,7 +239,7 @@ def test_debugger_conditional_line_probe_on_instance_method(): line=36, condition=DDExpression(dsl="True", callable=dd_compile(True)), ), - lambda: Stuff().instancestuff(), + lambda: stuff.Stuff().instancestuff(), ) (snapshot,) = snapshots @@ -258,7 +252,7 @@ def test_debugger_conditional_line_probe_on_instance_method(): assert captures["locals"] == {} -def test_debugger_invalid_line(): +def test_debugger_invalid_line(stuff): with debugger() as d: d.add_probes( create_snapshot_line_probe( @@ -268,13 +262,13 @@ def test_debugger_invalid_line(): ), good_probe(), ) - Stuff().instancestuff() + stuff.Stuff().instancestuff() assert all(s["debugger"]["snapshot"]["probe"]["id"] != "invalidline" for s in d.uploader.wait_for_payloads()) @mock.patch("ddtrace.debugging._debugger.log") -def test_debugger_invalid_source_file(log): +def test_debugger_invalid_source_file(log, stuff): with debugger() as d: d.add_probes( create_snapshot_line_probe( @@ -284,7 +278,7 @@ def test_debugger_invalid_source_file(log): ), good_probe(), ) - Stuff().instancestuff() + stuff.Stuff().instancestuff() log.error.assert_called_once_with( "Cannot inject probe %s: source file %s cannot be resolved", "invalidsource", "tests/submod/bonkers.py" @@ -293,7 +287,7 @@ def test_debugger_invalid_source_file(log): assert all(s["debugger"]["snapshot"]["probe"]["id"] != "invalidsource" for s in d.uploader.wait_for_payloads()) -def test_debugger_decorated_method(): +def test_debugger_decorated_method(stuff): simple_debugger_test( create_snapshot_line_probe( probe_id="probe-decorated-method", @@ -301,7 +295,7 @@ def test_debugger_decorated_method(): line=48, condition=None, ), - Stuff().decoratedstuff, + stuff.Stuff().decoratedstuff, ) @@ -324,7 +318,7 @@ def test_debugger_max_probes(mock_log): mock_log.warning.assert_called_once_with("Too many active probes. Ignoring new ones.") -def test_debugger_tracer_correlation(): +def test_debugger_tracer_correlation(stuff): with debugger() as d: d.add_probes( create_snapshot_line_probe( @@ -338,16 +332,14 @@ def test_debugger_tracer_correlation(): with d._tracer.trace("test-span") as span: trace_id = format_trace_id(span.trace_id) span_id = str(span.span_id) - Stuff().instancestuff() + stuff.Stuff().instancestuff() snapshots = d.uploader.wait_for_payloads() assert all(snapshot["dd"]["trace_id"] == trace_id for snapshot in snapshots) assert all(snapshot["dd"]["span_id"] == span_id for snapshot in snapshots) -def test_debugger_captured_exception(): - from tests.submod import stuff - +def test_debugger_captured_exception(stuff): snapshots = simple_debugger_test( create_snapshot_line_probe( probe_id="captured-exception-test", @@ -364,7 +356,7 @@ def test_debugger_captured_exception(): assert captures["throwable"]["type"] == "Exception" -def test_debugger_multiple_threads(): +def test_debugger_multiple_threads(stuff): with debugger() as d: probes = [ good_probe(), @@ -372,7 +364,7 @@ def test_debugger_multiple_threads(): ] d.add_probes(*probes) - callables = [Stuff().instancestuff, lambda: Stuff().propertystuff] + callables = [stuff.Stuff().instancestuff, lambda: stuff.Stuff().propertystuff] threads = [Thread(target=callables[_ % len(callables)]) for _ in range(10)] for t in threads: @@ -409,59 +401,57 @@ def create_stuff_line_metric_probe(kind, value=None): ) -def test_debugger_metric_probe_simple_count(mock_metrics): +def test_debugger_metric_probe_simple_count(mock_metrics, stuff): with debugger() as d: d.add_probes(create_stuff_line_metric_probe(MetricProbeKind.COUNTER)) - Stuff().instancestuff() + stuff.Stuff().instancestuff() assert ( call("probe.test.counter", 1.0, ["foo:bar", "debugger.probeid:metric-probe-test"]) in mock_metrics.increment.mock_calls ) -def test_debugger_metric_probe_count_value(mock_metrics): +def test_debugger_metric_probe_count_value(mock_metrics, stuff): with debugger() as d: d.add_probes(create_stuff_line_metric_probe(MetricProbeKind.COUNTER, {"ref": "bar"})) - Stuff().instancestuff(40) + stuff.Stuff().instancestuff(40) assert ( call("probe.test.counter", 40.0, ["foo:bar", "debugger.probeid:metric-probe-test"]) in mock_metrics.increment.mock_calls ) -def test_debugger_metric_probe_guage_value(mock_metrics): +def test_debugger_metric_probe_guage_value(mock_metrics, stuff): with debugger() as d: d.add_probes(create_stuff_line_metric_probe(MetricProbeKind.GAUGE, {"ref": "bar"})) - Stuff().instancestuff(41) + stuff.Stuff().instancestuff(41) assert ( call("probe.test.counter", 41.0, ["foo:bar", "debugger.probeid:metric-probe-test"]) in mock_metrics.gauge.mock_calls ) -def test_debugger_metric_probe_histogram_value(mock_metrics): +def test_debugger_metric_probe_histogram_value(mock_metrics, stuff): with debugger() as d: d.add_probes(create_stuff_line_metric_probe(MetricProbeKind.HISTOGRAM, {"ref": "bar"})) - Stuff().instancestuff(42) + stuff.Stuff().instancestuff(42) assert ( call("probe.test.counter", 42.0, ["foo:bar", "debugger.probeid:metric-probe-test"]) in mock_metrics.histogram.mock_calls ) -def test_debugger_metric_probe_distribution_value(mock_metrics): +def test_debugger_metric_probe_distribution_value(mock_metrics, stuff): with debugger() as d: d.add_probes(create_stuff_line_metric_probe(MetricProbeKind.DISTRIBUTION, {"ref": "bar"})) - Stuff().instancestuff(43) + stuff.Stuff().instancestuff(43) assert ( call("probe.test.counter", 43.0, ["foo:bar", "debugger.probeid:metric-probe-test"]) in mock_metrics.distribution.mock_calls ) -def test_debugger_multiple_function_probes_on_same_function(): - global Stuff - +def test_debugger_multiple_function_probes_on_same_function(stuff): probes = [ create_snapshot_function_probe( probe_id="probe-instance-method-%d" % i, @@ -475,9 +465,9 @@ def test_debugger_multiple_function_probes_on_same_function(): with debugger() as d: d.add_probes(*probes) - wrapping_context = DebuggerWrappingContext.extract(Stuff.instancestuff) + wrapping_context = DebuggerWrappingContext.extract(stuff.Stuff.instancestuff) assert wrapping_context.probes == {probe.probe_id: probe for probe in probes} - Stuff().instancestuff(42) + stuff.Stuff().instancestuff(42) d.collector.wait( lambda q: Counter(s.probe.probe_id for s in q) @@ -492,7 +482,7 @@ def test_debugger_multiple_function_probes_on_same_function(): assert "probe-instance-method-1" not in wrapping_context.probes - Stuff().instancestuff(42) + stuff.Stuff().instancestuff(42) d.collector.wait( lambda q: Counter(s.probe.probe_id for s in q) @@ -505,7 +495,7 @@ def test_debugger_multiple_function_probes_on_same_function(): d.remove_probes(probes[0], probes[2]) - Stuff().instancestuff(42) + stuff.Stuff().instancestuff(42) assert Counter(s.probe.probe_id for s in d.test_queue) == { "probe-instance-method-0": 2, @@ -514,12 +504,10 @@ def test_debugger_multiple_function_probes_on_same_function(): } with pytest.raises(AttributeError): - Stuff.instancestuff.__dd_wrappers__ + stuff.Stuff.instancestuff.__dd_wrappers__ def test_debugger_multiple_function_probes_on_same_lazy_module(): - sys.modules.pop("tests.submod.stuff", None) - probes = [ create_snapshot_function_probe( probe_id="probe-instance-method-%d" % i, diff --git a/tests/submod/custom_decorated_stuff.py b/tests/submod/custom_decorated_stuff.py new file mode 100644 index 00000000000..1150859b6dd --- /dev/null +++ b/tests/submod/custom_decorated_stuff.py @@ -0,0 +1,17 @@ +class App: + def __init__(self): + self.views = {} + + def route(self, path): + def wrapper(view): + self.views[path] = view + + return wrapper + + +app = App() + + +@app.route("/home") +def home(): + pass From 073f7f5e0dcb620d10e85e63c2dbc0974a697280 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:42:57 +0100 Subject: [PATCH 309/372] chore(ci): upgrade python for build action (#11735) python 3.7 is no longer supported on ubuntu. This PR update the python version for the build action to 3.12 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/build_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index 77d52c757f5..df5184f83e5 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.7' + python-version: '3.12' - name: Build sdist run: | pip install "setuptools_scm[toml]>=4" "cython" "cmake>=3.24.2,<3.28" "setuptools-rust" From 1e3bcc778f54631f3dbc9e1010262a2e1ba878e9 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:24:38 +0000 Subject: [PATCH 310/372] chore(ci_visibility): support pytest-benchmark plugin in v2 of pytest plugin (#11736) This updates the v2 pytest plugin to support adding benchmark data from the `pytest-benchmark` plugin. In the past, this was provided through the `ddtrace.pytest_benchmark` plugin, but this moves the logic into the main `ddtrace` plugin in order to be able to deprecate the smaller plugin. A new `_set_item_tags()` method is added to the TestVisibility item classes to be able to modify/add tags that would otherwise be set by the parent class. This is slightly different from the existing `_set_span_tags()` that is mostly intended to manipulate tags on the span directly. The constants in the `pytest_benchmark` plugin directory are somewhat clumsy and should be cleaned up in `ddtrace==3.0.0`. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/498209d.txt | 22 ++++ .riot/requirements/e5cd460.txt | 22 ++++ ddtrace/contrib/pytest/_benchmark_utils.py | 35 ++++++ ddtrace/contrib/pytest/_plugin_v2.py | 5 + ddtrace/contrib/pytest_benchmark/constants.py | 21 ++++ ddtrace/contrib/pytest_benchmark/plugin.py | 14 ++- ddtrace/internal/ci_visibility/api/_base.py | 7 +- ddtrace/internal/ci_visibility/api/_test.py | 28 ++++- ddtrace/internal/ci_visibility/constants.py | 1 + ddtrace/internal/ci_visibility/recorder.py | 11 ++ .../test_visibility/_benchmark_mixin.py | 75 ++++++++++++ ddtrace/internal/test_visibility/api.py | 3 +- riotfile.py | 32 ++--- .../pytest_benchmark/test_pytest_benchmark.py | 110 +++--------------- 14 files changed, 274 insertions(+), 112 deletions(-) create mode 100644 .riot/requirements/498209d.txt create mode 100644 .riot/requirements/e5cd460.txt create mode 100644 ddtrace/contrib/pytest/_benchmark_utils.py create mode 100644 ddtrace/internal/test_visibility/_benchmark_mixin.py diff --git a/.riot/requirements/498209d.txt b/.riot/requirements/498209d.txt new file mode 100644 index 00000000000..b975548628c --- /dev/null +++ b/.riot/requirements/498209d.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/498209d.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +py-cpuinfo==9.0.0 +pytest==8.3.4 +pytest-benchmark==4.0.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/e5cd460.txt b/.riot/requirements/e5cd460.txt new file mode 100644 index 00000000000..d14867cc689 --- /dev/null +++ b/.riot/requirements/e5cd460.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e5cd460.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +py-cpuinfo==9.0.0 +pytest==8.3.4 +pytest-benchmark==4.0.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/ddtrace/contrib/pytest/_benchmark_utils.py b/ddtrace/contrib/pytest/_benchmark_utils.py new file mode 100644 index 00000000000..77dd6061b13 --- /dev/null +++ b/ddtrace/contrib/pytest/_benchmark_utils.py @@ -0,0 +1,35 @@ +import pytest + +from ddtrace.contrib.pytest._utils import _get_test_id_from_item +from ddtrace.contrib.pytest_benchmark.constants import PLUGIN_METRICS_V2 +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._benchmark_mixin import BenchmarkDurationData +from ddtrace.internal.test_visibility.api import InternalTest + + +log = get_logger(__name__) + + +def _set_benchmark_data_from_item(item: pytest.Item) -> None: + try: + fixture = hasattr(item, "funcargs") and item.funcargs.get("benchmark") + + if not fixture or not fixture.stats: + return + + stat_object = item.funcargs.get("benchmark").stats.stats + + data_kwargs = {} + + for data_attr, stats_attr in PLUGIN_METRICS_V2.items(): + if hasattr(stat_object, stats_attr): + data_kwargs[data_attr] = getattr(stat_object, stats_attr) + + test_id = _get_test_id_from_item(item) + benchmark_data = BenchmarkDurationData(**data_kwargs) + + InternalTest.set_benchmark_data(test_id, benchmark_data, is_benchmark=True) + + except Exception: # noqa: E722 + log.debug("Unable to set benchmark data for item %s", item, exc_info=True) + return None diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index e9420f62527..e51739ccee9 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -13,6 +13,7 @@ from ddtrace.contrib.internal.coverage.patch import run_coverage_report from ddtrace.contrib.internal.coverage.utils import _is_coverage_invoked_by_coverage_run from ddtrace.contrib.internal.coverage.utils import _is_coverage_patched +from ddtrace.contrib.pytest._benchmark_utils import _set_benchmark_data_from_item from ddtrace.contrib.pytest._plugin_v1 import _extract_reason from ddtrace.contrib.pytest._plugin_v1 import _is_pytest_cov_enabled from ddtrace.contrib.pytest._types import _pytest_report_teststatus_return_type @@ -457,6 +458,10 @@ def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome if test_outcome.status is None and call.when != "teardown": return + # Support for pytest-benchmark plugin + if item.config.pluginmanager.hasplugin("benchmark"): + _set_benchmark_data_from_item(item) + # Record a result if we haven't already recorded it: if not InternalTest.is_finished(test_id): InternalTest.finish(test_id, test_outcome.status, test_outcome.skip_reason, test_outcome.exc_info) diff --git a/ddtrace/contrib/pytest_benchmark/constants.py b/ddtrace/contrib/pytest_benchmark/constants.py index 974208509f7..b4c4f7f5b27 100644 --- a/ddtrace/contrib/pytest_benchmark/constants.py +++ b/ddtrace/contrib/pytest_benchmark/constants.py @@ -56,3 +56,24 @@ STATISTICS_STDDEV_OUTLIERS: PLUGIN_STDDEV_OUTLIERS, STATISTICS_TOTAL: PLUGIN_TOTAL, } + +PLUGIN_METRICS_V2 = { + "duration_mean": PLUGIN_MEAN, + "duration_runs": PLUGIN_ROUNDS, + "statistics_hd15iqr": PLUGIN_HD15IQR, + "statistics_iqr": PLUGIN_IQR, + "statistics_iqr_outliers": PLUGIN_IQR_OUTLIERS, + "statistics_ld15iqr": PLUGIN_LD15IQR, + "statistics_max": PLUGIN_MAX, + "statistics_mean": PLUGIN_MEAN, + "statistics_median": PLUGIN_MEDIAN, + "statistics_min": PLUGIN_MIN, + "statistics_n": PLUGIN_ROUNDS, + "statistics_ops": PLUGIN_OPS, + "statistics_outliers": PLUGIN_OUTLIERS, + "statistics_q1": PLUGIN_Q1, + "statistics_q3": PLUGIN_Q3, + "statistics_std_dev": PLUGIN_STDDEV, + "statistics_std_dev_outliers": PLUGIN_STDDEV_OUTLIERS, + "statistics_total": PLUGIN_TOTAL, +} diff --git a/ddtrace/contrib/pytest_benchmark/plugin.py b/ddtrace/contrib/pytest_benchmark/plugin.py index 461b5f931ac..4cb76148dbc 100644 --- a/ddtrace/contrib/pytest_benchmark/plugin.py +++ b/ddtrace/contrib/pytest_benchmark/plugin.py @@ -1,9 +1,19 @@ +from ddtrace import DDTraceDeprecationWarning +from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 from ddtrace.contrib.pytest.plugin import is_enabled as is_ddtrace_enabled +from ddtrace.vendor.debtcollector import deprecate def pytest_configure(config): if config.pluginmanager.hasplugin("benchmark") and config.pluginmanager.hasplugin("ddtrace"): if is_ddtrace_enabled(config): - from ._plugin import _PytestBenchmarkPlugin + deprecate( + "this version of the ddtrace.pytest_benchmark plugin is deprecated", + message="it will be integrated with the main pytest ddtrace plugin", + removal_version="3.0.0", + category=DDTraceDeprecationWarning, + ) + if not _USE_PLUGIN_V2: + from ._plugin import _PytestBenchmarkPlugin - config.pluginmanager.register(_PytestBenchmarkPlugin(), "_datadog-pytest-benchmark") + config.pluginmanager.register(_PytestBenchmarkPlugin(), "_datadog-pytest-benchmark") diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index dbaa48d1af3..f1e2cd2b3b0 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -207,7 +207,8 @@ def _finish_span(self, override_finish_time: Optional[float] = None) -> None: if self._session_settings.atr_settings is not None and self._session_settings.atr_settings.enabled: self._set_atr_tags() - # Allow item-level _set_span_tags() to potentially overwrite default and hierarchy tags. + # Allow items to potentially overwrite default and hierarchy tags. + self._set_item_tags() self._set_span_tags() self._add_all_tags_to_span() @@ -247,6 +248,10 @@ def _set_default_tags(self) -> None: if self._source_file_info.end_line is not None: self.set_tag(test.SOURCE_END, self._source_file_info.end_line) + def _set_item_tags(self) -> None: + """Overridable by subclasses to set tags specific to the item type""" + pass + def _set_itr_tags(self, itr_enabled: bool) -> None: """Note: some tags are also added in the parent class as well as some individual item classes""" if not itr_enabled: diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index c0eb615cd03..7a61473ff92 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -5,6 +5,7 @@ from typing import Optional from typing import Union +from ddtrace.contrib.pytest_benchmark.constants import BENCHMARK_INFO from ddtrace.ext import SpanTypes from ddtrace.ext import test from ddtrace.ext.test_visibility import ITR_SKIPPING_LEVEL @@ -17,6 +18,7 @@ from ddtrace.internal.ci_visibility.api._base import TestVisibilityItemBase from ddtrace.internal.ci_visibility.api._base import TestVisibilitySessionSettings from ddtrace.internal.ci_visibility.api._coverage_data import TestVisibilityCoverageData +from ddtrace.internal.ci_visibility.constants import BENCHMARK from ddtrace.internal.ci_visibility.constants import TEST from ddtrace.internal.ci_visibility.constants import TEST_EFD_ABORT_REASON from ddtrace.internal.ci_visibility.constants import TEST_IS_NEW @@ -25,6 +27,8 @@ from ddtrace.internal.ci_visibility.telemetry.events import record_event_created_test from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished_test from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._benchmark_mixin import BENCHMARK_TAG_MAP +from ddtrace.internal.test_visibility._benchmark_mixin import BenchmarkDurationData from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from ddtrace.internal.test_visibility.coverage_lines import CoverageLines @@ -78,8 +82,8 @@ def __init__( self._atr_is_retry = is_atr_retry self._atr_retries: List[TestVisibilityTest] = [] - # Currently unsupported - self._is_benchmark = None + self._is_benchmark = False + self._benchmark_duration_data: Optional[BenchmarkDurationData] = None def __repr__(self) -> str: suite_name = self.parent.name if self.parent is not None else "none" @@ -93,6 +97,11 @@ def _get_hierarchy_tags(self) -> Dict[str, str]: test.NAME: self.name, } + def _set_item_tags(self) -> None: + """Overrides parent tags for cases where they need to be modified""" + if self._is_benchmark: + self.set_tag(test.TYPE, BENCHMARK) + def _set_efd_tags(self) -> None: if self._efd_is_retry: self.set_tag(TEST_IS_RETRY, self._efd_is_retry) @@ -398,3 +407,18 @@ def _get_browser_driver(self): if self._span is None: return None return self._span.get_tag("test.browser.driver") + + # + # Benchmark test functionality + # + def set_benchmark_data(self, duration_data: Optional[BenchmarkDurationData], is_benchmark: bool = True): + self._benchmark_duration_data = duration_data + self._is_benchmark = is_benchmark + + if self._benchmark_duration_data is not None: + self.set_tag(BENCHMARK_INFO, "Time") + + for tag, attr in BENCHMARK_TAG_MAP.items(): + value = getattr(self._benchmark_duration_data, tag) + if value is not None: + self.set_tag(attr, value) diff --git a/ddtrace/internal/ci_visibility/constants.py b/ddtrace/internal/ci_visibility/constants.py index f30b6743f5a..7ace37b9424 100644 --- a/ddtrace/internal/ci_visibility/constants.py +++ b/ddtrace/internal/ci_visibility/constants.py @@ -4,6 +4,7 @@ SUITE = "suite" TEST = "test" +BENCHMARK = "benchmark" EVENT_TYPE = "type" diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 225221a4a7d..b306a1e7912 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -74,6 +74,7 @@ from ddtrace.internal.service import Service from ddtrace.internal.test_visibility._atr_mixins import ATRTestMixin from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings +from ddtrace.internal.test_visibility._benchmark_mixin import BenchmarkTestMixin from ddtrace.internal.test_visibility._efd_mixins import EFDTestMixin from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -1181,6 +1182,15 @@ def _on_set_test_parameters(item_id: TestId, parameters: str): CIVisibility.get_test_by_id(item_id).set_parameters(parameters) +@_requires_civisibility_enabled +def _on_set_benchmark_data(set_benchmark_data_args: BenchmarkTestMixin.SetBenchmarkDataArgs): + item_id = set_benchmark_data_args.test_id + data = set_benchmark_data_args.benchmark_data + is_benchmark = set_benchmark_data_args.is_benchmark + log.debug("Handling set benchmark data for test id %s, data %s, is_benchmark %s", item_id, data, is_benchmark) + CIVisibility.get_test_by_id(item_id).set_benchmark_data(data, is_benchmark) + + def _register_test_handlers(): log.debug("Registering test handlers") core.on("test_visibility.test.discover", _on_discover_test) @@ -1188,6 +1198,7 @@ def _register_test_handlers(): core.on("test_visibility.test.start", _on_start_test) core.on("test_visibility.test.finish", _on_finish_test) core.on("test_visibility.test.set_parameters", _on_set_test_parameters) + core.on("test_visibility.test.set_benchmark_data", _on_set_benchmark_data) @_requires_civisibility_enabled diff --git a/ddtrace/internal/test_visibility/_benchmark_mixin.py b/ddtrace/internal/test_visibility/_benchmark_mixin.py new file mode 100644 index 00000000000..c41d45b10a5 --- /dev/null +++ b/ddtrace/internal/test_visibility/_benchmark_mixin.py @@ -0,0 +1,75 @@ +import typing as t + +from ddtrace.ext.test_visibility._utils import _catch_and_log_exceptions +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId + + +log = get_logger(__name__) + + +class BenchmarkDurationData(t.NamedTuple): + duration_info: t.Optional[str] = None + duration_mean: t.Optional[float] = None + duration_runs: t.Optional[int] = None + statistics_hd15iqr: t.Optional[float] = None + statistics_iqr: t.Optional[float] = None + statistics_iqr_outliers: t.Optional[float] = None + statistics_ld15iqr: t.Optional[float] = None + statistics_max: t.Optional[float] = None + statistics_mean: t.Optional[float] = None + statistics_median: t.Optional[float] = None + statistics_min: t.Optional[float] = None + statistics_n: t.Optional[float] = None + statistics_ops: t.Optional[float] = None + statistics_outliers: t.Optional[float] = None + statistics_q1: t.Optional[float] = None + statistics_q3: t.Optional[float] = None + statistics_std_dev: t.Optional[float] = None + statistics_std_dev_outliers: t.Optional[float] = None + statistics_total: t.Optional[float] = None + + +class BenchmarkTestMixin: + class SetBenchmarkDataArgs(t.NamedTuple): + test_id: InternalTestId + benchmark_data: t.Optional[BenchmarkDurationData] + is_benchmark: bool = True + + @classmethod + @_catch_and_log_exceptions + def set_benchmark_data( + cls, + item_id: InternalTestId, + benchmark_data: t.Optional[BenchmarkDurationData] = None, + is_benchmark: bool = True, + ): + log.debug("Setting benchmark data for test %s: %s", item_id, benchmark_data) + core.dispatch( + "test_visibility.test.set_benchmark_data", + (BenchmarkTestMixin.SetBenchmarkDataArgs(item_id, benchmark_data, is_benchmark),), + ) + + +BENCHMARK_TAG_MAP = { + "duration_info": "benchmark.duration.info", + "duration_mean": "benchmark.duration.mean", + "duration_runs": "benchmark.duration.runs", + "statistics_hd15iqr": "benchmark.duration.statistics.hd15iqr", + "statistics_iqr": "benchmark.duration.statistics.iqr", + "statistics_iqr_outliers": "benchmark.duration.statistics.iqr_outliers", + "statistics_ld15iqr": "benchmark.duration.statistics.ld15iqr", + "statistics_max": "benchmark.duration.statistics.max", + "statistics_mean": "benchmark.duration.statistics.mean", + "statistics_median": "benchmark.duration.statistics.median", + "statistics_min": "benchmark.duration.statistics.min", + "statistics_n": "benchmark.duration.statistics.n", + "statistics_ops": "benchmark.duration.statistics.ops", + "statistics_outliers": "benchmark.duration.statistics.outliers", + "statistics_q1": "benchmark.duration.statistics.q1", + "statistics_q3": "benchmark.duration.statistics.q3", + "statistics_std_dev": "benchmark.duration.statistics.std_dev", + "statistics_std_dev_outliers": "benchmark.duration.statistics.std_dev_outliers", + "statistics_total": "benchmark.duration.statistics.total", +} diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index c5084d320cb..d66dbcc32c7 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -14,6 +14,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.test_visibility._atr_mixins import ATRSessionMixin from ddtrace.internal.test_visibility._atr_mixins import ATRTestMixin +from ddtrace.internal.test_visibility._benchmark_mixin import BenchmarkTestMixin from ddtrace.internal.test_visibility._efd_mixins import EFDSessionMixin from ddtrace.internal.test_visibility._efd_mixins import EFDTestMixin from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId @@ -132,7 +133,7 @@ class InternalTestSuite(ext_api.TestSuite, InternalTestBase, ITRMixin): pass -class InternalTest(ext_api.Test, InternalTestBase, ITRMixin, EFDTestMixin, ATRTestMixin): +class InternalTest(ext_api.Test, InternalTestBase, ITRMixin, EFDTestMixin, ATRTestMixin, BenchmarkTestMixin): class FinishArgs(NamedTuple): """InternalTest allows finishing with an overridden finish time (for EFD and other retry purposes)""" diff --git a/riotfile.py b/riotfile.py index b12bfcc1181..653023da524 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1723,26 +1723,32 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="pytest-benchmark", + pys=select_pys(min_version="3.7", max_version="3.12"), command="pytest {cmdargs} --no-ddtrace --no-cov tests/contrib/pytest_benchmark/", pkgs={ "msgpack": latest, "pytest-randomly": latest, }, - env={ - "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", - }, venvs=[ Venv( - venvs=[ - Venv( - pys=select_pys(min_version="3.7", max_version="3.10"), - pkgs={ - "pytest-benchmark": [ - ">=3.1.0,<=4.0.0", - ] - }, - ) - ], + pkgs={ + "pytest-benchmark": [ + ">=3.1.0,<=4.0.0", + ] + }, + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + }, + ), + Venv( + pkgs={ + "pytest-benchmark": [ + ">=3.1.0,<=4.0.0", + ] + }, + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + }, ), ], ), diff --git a/tests/contrib/pytest_benchmark/test_pytest_benchmark.py b/tests/contrib/pytest_benchmark/test_pytest_benchmark.py index eedb634ccce..ba55659b8f8 100644 --- a/tests/contrib/pytest_benchmark/test_pytest_benchmark.py +++ b/tests/contrib/pytest_benchmark/test_pytest_benchmark.py @@ -1,10 +1,5 @@ import os -from unittest import mock -import pytest - -import ddtrace -from ddtrace.contrib.pytest.plugin import is_enabled from ddtrace.contrib.pytest_benchmark.constants import BENCHMARK_INFO from ddtrace.contrib.pytest_benchmark.constants import BENCHMARK_MEAN from ddtrace.contrib.pytest_benchmark.constants import BENCHMARK_RUN @@ -25,51 +20,10 @@ from ddtrace.contrib.pytest_benchmark.constants import STATISTICS_STDDEV_OUTLIERS from ddtrace.contrib.pytest_benchmark.constants import STATISTICS_TOTAL from ddtrace.ext.test import TEST_TYPE -from ddtrace.internal.ci_visibility import CIVisibility -from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings -from tests.ci_visibility.test_encoder import _patch_dummy_writer -from tests.utils import TracerTestCase -from tests.utils import override_env - - -class PytestTestCase(TracerTestCase): - @pytest.fixture(autouse=True) - def fixtures(self, testdir, monkeypatch, git_repo): - self.testdir = testdir - self.monkeypatch = monkeypatch - self.git_repo = git_repo - - @pytest.fixture(autouse=True) - def _dummy_check_enabled_features(self): - """By default, assume that _check_enabled_features() returns an ITR-disabled response. - - Tests that need a different response should re-patch the CIVisibility object. - """ - with mock.patch( - "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", - return_value=TestVisibilityAPISettings(False, False, False, False), - ): - yield +from tests.contrib.pytest.test_pytest import PytestTestCaseBase - def inline_run(self, *args): - """Execute test script with test tracer.""" - - class CIVisibilityPlugin: - @staticmethod - def pytest_configure(config): - if is_enabled(config): - with _patch_dummy_writer(): - assert CIVisibility.enabled - CIVisibility.disable() - CIVisibility.enable(tracer=self.tracer, config=ddtrace.config.pytest) - - with override_env(dict(DD_API_KEY="foobar.baz")): - return self.testdir.inline_run(*args, plugins=[CIVisibilityPlugin()]) - - def subprocess_run(self, *args): - """Execute test script with test tracer.""" - return self.testdir.runpytest_subprocess(*args) +class PytestTestCase(PytestTestCaseBase): def test_span_contains_benchmark(self): """Test with benchmark.""" py_file = self.testdir.makepyfile( @@ -93,54 +47,24 @@ def test_sum_longer(benchmark): assert spans[0].get_tag(TEST_TYPE) == "benchmark" assert spans[0].get_tag(BENCHMARK_INFO) == "Time" - assert isinstance(spans[0].get_metric(BENCHMARK_MEAN), float) or isinstance( - spans[0].get_metric(BENCHMARK_MEAN), int - ) + assert isinstance(spans[0].get_metric(BENCHMARK_MEAN), (float, int)) assert isinstance(spans[0].get_metric(BENCHMARK_RUN), int) - assert isinstance(spans[0].get_metric(STATISTICS_HD15IQR), float) or isinstance( - spans[0].get_metric(STATISTICS_HD15IQR), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_IQR), float) or isinstance( - spans[0].get_metric(STATISTICS_IQR), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_IQR_OUTLIERS), int) or isinstance( - spans[0].get_metric(STATISTICS_IQR_OUTLIERS), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_LD15IQR), float) or isinstance( - spans[0].get_metric(STATISTICS_LD15IQR), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_MAX), float) or isinstance( - spans[0].get_metric(STATISTICS_MAX), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_MEAN), float) or isinstance( - spans[0].get_metric(STATISTICS_MEAN), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_MEDIAN), float) or isinstance( - spans[0].get_metric(STATISTICS_MEDIAN), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_MIN), float) or isinstance( - spans[0].get_metric(STATISTICS_MIN), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_OPS), float) or isinstance( - spans[0].get_metric(STATISTICS_OPS), int - ) + assert isinstance(spans[0].get_metric(STATISTICS_HD15IQR), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_IQR), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_IQR_OUTLIERS), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_LD15IQR), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_MAX), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_MEAN), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_MEDIAN), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_MIN), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_OPS), (float, int)) assert isinstance(spans[0].get_tag(STATISTICS_OUTLIERS), str) - assert isinstance(spans[0].get_metric(STATISTICS_Q1), float) or isinstance( - spans[0].get_metric(STATISTICS_Q1), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_Q3), float) or isinstance( - spans[0].get_metric(STATISTICS_Q3), int - ) + assert isinstance(spans[0].get_metric(STATISTICS_Q1), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_Q3), (float, int)) assert isinstance(spans[0].get_metric(STATISTICS_N), int) - assert isinstance(spans[0].get_metric(STATISTICS_STDDEV), float) or isinstance( - spans[0].get_metric(STATISTICS_STDDEV), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_STDDEV_OUTLIERS), float) or isinstance( - spans[0].get_metric(STATISTICS_STDDEV_OUTLIERS), int - ) - assert isinstance(spans[0].get_metric(STATISTICS_TOTAL), float) or isinstance( - spans[0].get_metric(STATISTICS_TOTAL), int - ) + assert isinstance(spans[0].get_metric(STATISTICS_STDDEV), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_STDDEV_OUTLIERS), (float, int)) + assert isinstance(spans[0].get_metric(STATISTICS_TOTAL), (float, int)) assert spans[0].get_metric(BENCHMARK_MEAN) > 0.0002 assert spans[0].get_metric(BENCHMARK_RUN) > 0 From f2841926d9c8b9b1387bcad262a8e35aff9ab018 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:09:21 +0100 Subject: [PATCH 311/372] feat(asm): add stack trace report for iast (#11574) - Add stack trace report to IAST events - Update test-agent to 1.20.0 to add support for stack traces report using msgpack (https://github.com/DataDog/images/pull/6234) - factorize stack trace code already used for exploit prevention, to be usable for iast too. - improve code of `ddtrace_iast_flask_patch` so it gets compatible with the new feature - improve `test_ddtrace_iast_flask_patch` to check that + operator was properly replaced. - add stack trace report unit test on threat tests System tests will also be enabled with https://github.com/DataDog/system-tests/pull/3663 APPSEC-55904 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 2 +- .gitlab/services.yml | 2 +- ddtrace/appsec/_constants.py | 7 ++- .../_exploit_prevention/stack_traces.py | 44 ++++++++++++------- ddtrace/appsec/_iast/__init__.py | 9 +++- ddtrace/appsec/_iast/_ast/ast_patching.py | 27 +----------- ddtrace/appsec/_iast/_iast_request_context.py | 9 ++++ ddtrace/appsec/_iast/reporter.py | 12 +++++ ddtrace/appsec/_processor.py | 6 +-- docker-compose.yml | 2 +- ...tack_traces_for_iast-cd2c008168f6181e.yaml | 4 ++ tests/appsec/contrib_appsec/conftest.py | 13 +++++- tests/appsec/contrib_appsec/utils.py | 23 ++++++---- .../integrations/pygoat_tests/test_pygoat.py | 3 +- .../test_flask_entrypoint_iast_patches.py | 13 ++++-- 15 files changed, 111 insertions(+), 65 deletions(-) create mode 100644 releasenotes/notes/stack_traces_for_iast-cd2c008168f6181e.yaml diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 0dee5155002..73994eab222 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -16,7 +16,7 @@ mongo_image: &mongo_image mongo:3.6@sha256:19c11a8f1064fd2bb713ef1270f79a742a184 httpbin_image: &httpbin_image kennethreitz/httpbin@sha256:2c7abc4803080c22928265744410173b6fea3b898872c01c5fd0f0f9df4a59fb vertica_image: &vertica_image vertica/vertica-ce:latest rabbitmq_image: &rabbitmq_image rabbitmq:3.7-alpine -testagent_image: &testagent_image ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.17.0 +testagent_image: &testagent_image ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.20.0 parameters: coverage: diff --git a/.gitlab/services.yml b/.gitlab/services.yml index 3adcb973e89..51e28c38cc5 100644 --- a/.gitlab/services.yml +++ b/.gitlab/services.yml @@ -12,7 +12,7 @@ DD_REMOTE_CONFIGURATION_REFRESH_INTERVAL: 5s DD_DOGSTATSD_NON_LOCAL_TRAFFIC: true testagent: - name: registry.ddbuild.io/images/mirror/dd-apm-test-agent/ddapm-test-agent:v1.17.0 + name: registry.ddbuild.io/images/mirror/dd-apm-test-agent/ddapm-test-agent:v1.20.0 alias: testagent variables: LOG_LEVEL: INFO diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index fbb1264f6f9..83cb53e78ff 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -328,7 +328,6 @@ class DEFAULT(metaclass=Constant_Class): class EXPLOIT_PREVENTION(metaclass=Constant_Class): - STACK_TRACES: Literal["_dd.stack"] = "_dd.stack" STACK_TRACE_ID: Literal["stack_id"] = "stack_id" EP_ENABLED: Literal["DD_APPSEC_RASP_ENABLED"] = "DD_APPSEC_RASP_ENABLED" STACK_TRACE_ENABLED: Literal["DD_APPSEC_STACK_TRACE_ENABLED"] = "DD_APPSEC_STACK_TRACE_ENABLED" @@ -358,3 +357,9 @@ class FINGERPRINTING(metaclass=Constant_Class): HEADER = PREFIX + "http.header" NETWORK = PREFIX + "http.network" SESSION = PREFIX + "session" + + +class STACK_TRACE(metaclass=Constant_Class): + RASP = "exploit" + IAST = "vulnerability" + TAG: Literal["_dd.stack"] = "_dd.stack" diff --git a/ddtrace/appsec/_exploit_prevention/stack_traces.py b/ddtrace/appsec/_exploit_prevention/stack_traces.py index 8276d0c51bc..8d32a028ab9 100644 --- a/ddtrace/appsec/_exploit_prevention/stack_traces.py +++ b/ddtrace/appsec/_exploit_prevention/stack_traces.py @@ -3,36 +3,48 @@ from typing import Any from typing import Dict from typing import Iterable -from typing import List from typing import Optional from ddtrace._trace.span import Span -from ddtrace.appsec._constants import EXPLOIT_PREVENTION +from ddtrace.appsec._constants import STACK_TRACE from ddtrace.settings.asm import config as asm_config import ddtrace.tracer def report_stack( - message: str, span: Optional[Span] = None, crop_stack: Optional[str] = None, stack_id: Optional[str] = None -): + message: Optional[str] = None, + span: Optional[Span] = None, + crop_stack: Optional[str] = None, + stack_id: Optional[str] = None, + namespace: str = STACK_TRACE.RASP, +) -> bool: """ Report a stack trace to the current span. This is used to report stack traces for exploit prevention. Return the stack id for the reported stack trace to link it in triggers. """ - if not asm_config._ep_enabled or not asm_config._ep_stack_trace_enabled: - return None + if not asm_config._ep_stack_trace_enabled: + # stack trace report disabled + return False + if namespace == STACK_TRACE.RASP and not (asm_config._asm_enabled and asm_config._ep_enabled): + # exploit prevention stack trace with ep disabled + return False + if namespace == STACK_TRACE.IAST and not (asm_config._iast_enabled): + # iast stack trace with iast disabled + return False + if span is None: span = ddtrace.tracer.current_span() if span is None or stack_id is None: - return None + return False root_span = span._local_root or span - appsec_traces = root_span.get_struct_tag(EXPLOIT_PREVENTION.STACK_TRACES) or {} - exploit: List[Any] = appsec_traces.get("exploit", []) + appsec_traces = root_span.get_struct_tag(STACK_TRACE.TAG) or {} + current_list = appsec_traces.get(namespace, []) + total_length = len(current_list) # Do not report more than the maximum number of stack traces - if asm_config._ep_max_stack_traces and len(exploit) >= asm_config._ep_max_stack_traces: - return None + if asm_config._ep_max_stack_traces and total_length >= asm_config._ep_max_stack_traces: + return False stack = inspect.stack() if crop_stack is not None: @@ -43,8 +55,9 @@ def report_stack( res: Dict[str, Any] = { "language": "python", "id": stack_id, - "message": message, } + if message is not None: + res["message"] = message if len(stack) > asm_config._ep_max_stack_trace_depth > 0: top_stack = int(asm_config._ep_max_stack_trace_depth * asm_config._ep_stack_top_percent / 100) bottom_stack = asm_config._ep_max_stack_trace_depth - top_stack @@ -61,6 +74,7 @@ def report_stack( for i in iterator ] res["frames"] = frames - exploit.append(res) - appsec_traces["exploit"] = exploit - root_span.set_struct_tag(EXPLOIT_PREVENTION.STACK_TRACES, appsec_traces) + current_list.append(res) + appsec_traces[namespace] = current_list + root_span.set_struct_tag(STACK_TRACE.TAG, appsec_traces) + return True diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index fe488c87e46..724819b17df 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -31,6 +31,7 @@ def wrapped_function(wrapped, instance, args, kwargs): import inspect import os import sys +import types from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog @@ -61,7 +62,7 @@ def ddtrace_iast_flask_patch(): module_name = inspect.currentframe().f_back.f_globals["__name__"] module = sys.modules[module_name] try: - module_path, patched_ast = astpatch_module(module, remove_flask_run=True) + module_path, patched_ast = astpatch_module(module) except Exception: log.debug("Unexpected exception while AST patching", exc_info=True) return @@ -71,8 +72,12 @@ def ddtrace_iast_flask_patch(): return compiled_code = compile(patched_ast, module_path, "exec") + # creating a new module environment to execute the patched code from scratch + new_module = types.ModuleType(module_name) + module.__dict__.clear() + module.__dict__.update(new_module.__dict__) + # executing the compiled code in the new module environment exec(compiled_code, module.__dict__) # nosec B102 - sys.modules[module_name] = compiled_code _iast_propagation_enabled = False diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 6a1e4c2d3b6..7e2258bd556 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -3,7 +3,6 @@ import ast import codecs import os -import re from sys import builtin_module_names from sys import version_info import textwrap @@ -388,27 +387,6 @@ def visit_ast( return modified_ast -_FLASK_INSTANCE_REGEXP = re.compile(r"(\S*)\s*=.*Flask\(.*") - - -def _remove_flask_run(text: Text) -> Text: - """ - Find and remove flask app.run() call. This is used for patching - the app.py file and exec'ing to replace the module without creating - a new instance. - """ - flask_instance_name = re.search(_FLASK_INSTANCE_REGEXP, text) - if not flask_instance_name: - return text - groups = flask_instance_name.groups() - if not groups: - return text - - instance_name = groups[-1] - new_text = re.sub(instance_name + r"\.run\(.*\)", "pass", text) - return new_text - - _DIR_WRAPPER = textwrap.dedent( f""" @@ -442,7 +420,7 @@ def {_PREFIX}set_dir_filter(): ) -def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple[str, Optional[ast.Module]]: +def astpatch_module(module: ModuleType) -> Tuple[str, Optional[ast.Module]]: module_name = module.__name__ module_origin = origin(module) @@ -482,9 +460,6 @@ def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple log.debug("empty file: %s", module_path) return "", None - if remove_flask_run: - source_text = _remove_flask_run(source_text) - if not asbool(os.environ.get(IAST.ENV_NO_DIR_PATCH, "false")) and version_info > (3, 7): # Add the dir filter so __ddtrace stuff is not returned by dir(module) # does not work in 3.7 because it enters into infinite recursion diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index b711ae61195..a28c2d3ff0d 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -50,6 +50,7 @@ def __init__(self, span: Optional[Span] = None): self.request_enabled: bool = False self.iast_reporter: Optional[IastSpanReporter] = None self.iast_span_metrics: Dict[str, int] = {} + self.iast_stack_trace_id: int = 0 def _get_iast_context() -> Optional[IASTEnvironment]: @@ -96,6 +97,14 @@ def get_iast_reporter() -> Optional[IastSpanReporter]: return None +def get_iast_stacktrace_id() -> int: + env = _get_iast_context() + if env: + env.iast_stack_trace_id += 1 + return env.iast_stack_trace_id + return 0 + + def set_iast_request_enabled(request_enabled) -> None: env = _get_iast_context() if env: diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index c7004909cc9..62cc2ee8d65 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -11,6 +11,8 @@ from typing import Tuple import zlib +from ddtrace.appsec._constants import STACK_TRACE +from ddtrace.appsec._exploit_prevention.stack_traces import report_stack from ddtrace.appsec._iast._evidence_redaction import sensitive_handler from ddtrace.appsec._iast._utils import _get_source_index from ddtrace.appsec._iast.constants import VULN_INSECURE_HASHING_TYPE @@ -75,9 +77,19 @@ class Vulnerability(NotNoneDictable): evidence: Evidence location: Location hash: int = dataclasses.field(init=False, compare=False, hash=("PYTEST_CURRENT_TEST" in os.environ), repr=False) + stackId: Optional[str] = dataclasses.field(init=False, compare=False) def __post_init__(self): + # avoid circular import + from ddtrace.appsec._iast._iast_request_context import get_iast_stacktrace_id + self.hash = zlib.crc32(repr(self).encode()) + stacktrace_id = get_iast_stacktrace_id() + self.stackId = None + if stacktrace_id: + str_id = str(stacktrace_id) + if report_stack(stack_id=str_id, namespace=STACK_TRACE.IAST): + self.stackId = str_id def __repr__(self): return f"Vulnerability(type='{self.type}', location={self.location})" diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 4ba8222c89a..06328d1201a 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -21,10 +21,12 @@ from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import FINGERPRINTING from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._constants import STACK_TRACE from ddtrace.appsec._constants import WAF_ACTIONS from ddtrace.appsec._constants import WAF_DATA_NAMES from ddtrace.appsec._ddwaf import DDWaf_result from ddtrace.appsec._ddwaf.ddwaf_types import ddwaf_context_capsule +from ddtrace.appsec._exploit_prevention.stack_traces import report_stack from ddtrace.appsec._metrics import _set_waf_init_metric from ddtrace.appsec._metrics import _set_waf_request_metrics from ddtrace.appsec._metrics import _set_waf_updates_metric @@ -325,10 +327,8 @@ def _waf_action( blocked = parameters blocked[WAF_ACTIONS.TYPE] = "none" elif action == WAF_ACTIONS.STACK_ACTION: - from ddtrace.appsec._exploit_prevention.stack_traces import report_stack - stack_trace_id = parameters["stack_id"] - report_stack("exploit detected", span, crop_trace, stack_id=stack_trace_id) + report_stack("exploit detected", span, crop_trace, stack_id=stack_trace_id, namespace=STACK_TRACE.RASP) for rule in waf_results.data: rule[EXPLOIT_PREVENTION.STACK_TRACE_ID] = stack_trace_id diff --git a/docker-compose.yml b/docker-compose.yml index bed5a6ce8ee..cf3738c2dbe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -129,7 +129,7 @@ services: volumes: - ddagent:/tmp/ddagent:rw testagent: - image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.17.0 + image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.20.0 ports: - "127.0.0.1:9126:8126" volumes: diff --git a/releasenotes/notes/stack_traces_for_iast-cd2c008168f6181e.yaml b/releasenotes/notes/stack_traces_for_iast-cd2c008168f6181e.yaml new file mode 100644 index 00000000000..045552e9b7d --- /dev/null +++ b/releasenotes/notes/stack_traces_for_iast-cd2c008168f6181e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Code Security: This introduces stack trace reports for Code Security. diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index 9773ef124c9..74ad0f655ef 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -45,8 +45,17 @@ def check_waf_timeout(request): @pytest.fixture -def get_tag(root_span): - yield lambda name: root_span().get_tag(name) +def get_tag(test_spans, root_span): + # checking both root spans and web spans for the tag + def get(name): + for span in test_spans.spans: + if span.parent_id is None or span.span_type == "web": + res = span.get_tag(name) + if res is not None: + return res + return root_span().get_tag(name) + + yield get @pytest.fixture diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 0712e6d6fd8..0d195df764e 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -62,9 +62,13 @@ def location(self, response) -> str: def body(self, response) -> str: raise NotImplementedError + def get_stack_trace(self, root_span, namespace): + appsec_traces = root_span().get_struct_tag(asm_constants.STACK_TRACE.TAG) or {} + stacks = appsec_traces.get(namespace, []) + return stacks + def check_for_stack_trace(self, root_span): - appsec_traces = root_span().get_struct_tag(asm_constants.EXPLOIT_PREVENTION.STACK_TRACES) or {} - exploit = appsec_traces.get("exploit", []) + exploit = self.get_stack_trace(root_span, "exploit") stack_ids = sorted(set(t["id"] for t in exploit)) triggers = get_triggers(root_span()) stack_id_in_triggers = sorted(set(t["stack_id"] for t in (triggers or []) if "stack_id" in t)) @@ -1385,9 +1389,9 @@ def validate_top_function(trace): # there may have been multiple evaluations of other rules too assert (("rule_type", endpoint), ("waf_version", DDWAF_VERSION)) in evals if action_level == 2: - assert get_tag("rasp.request.done") is None + assert get_tag("rasp.request.done") is None, get_tag("rasp.request.done") else: - assert get_tag("rasp.request.done") == endpoint + assert get_tag("rasp.request.done") == endpoint, get_tag("rasp.request.done") assert get_metric(APPSEC.RASP_DURATION) is not None assert get_metric(APPSEC.RASP_DURATION_EXT) is not None assert get_metric(APPSEC.RASP_RULE_EVAL) is not None @@ -1398,7 +1402,7 @@ def validate_top_function(trace): assert "rasp" not in n assert get_triggers(root_span()) is None assert self.check_for_stack_trace(root_span) == [] - assert get_tag("rasp.request.done") == endpoint + assert get_tag("rasp.request.done") == endpoint, get_tag("rasp.request.done") @pytest.mark.parametrize("asm_enabled", [True, False]) @pytest.mark.parametrize("auto_events_enabled", [True, False]) @@ -1505,21 +1509,22 @@ def test_fingerprinting(self, interface, root_span, get_tag, asm_enabled, user_a assert get_tag(asm_constants.FINGERPRINTING.SESSION) is None def test_iast(self, interface, root_span, get_tag): - if interface.name == "fastapi" and asm_config._iast_enabled: - raise pytest.xfail("fastapi does not fully support IAST for now") - from ddtrace.ext import http - url = "/rasp/command_injection/?cmd=ls" + url = "/rasp/command_injection/?cmd=." self.update_tracer(interface) response = interface.client.get(url) assert self.status(response) == 200 assert get_tag(http.STATUS_CODE) == "200" assert self.body(response).startswith("command_injection endpoint") + stack_traces = self.get_stack_trace(root_span, "vulnerability") if asm_config._iast_enabled: assert get_tag("_dd.iast.json") is not None + # checking for iast stack traces + assert stack_traces else: assert get_tag("_dd.iast.json") is None + assert stack_traces == [] @contextmanager diff --git a/tests/appsec/integrations/pygoat_tests/test_pygoat.py b/tests/appsec/integrations/pygoat_tests/test_pygoat.py index e60d5336b35..f3dd0f173ee 100644 --- a/tests/appsec/integrations/pygoat_tests/test_pygoat.py +++ b/tests/appsec/integrations/pygoat_tests/test_pygoat.py @@ -26,6 +26,7 @@ def client(): agent_client = requests.session() reply = agent_client.get(TESTAGENT_URL + "/start" + TESTAGENT_TOKEN_PARAM, headers=TESTAGENT_HEADERS) + assert reply.status_code == 200 pygoat_client, token = login_to_pygoat() @@ -65,7 +66,7 @@ def get_traces(agent_client: requests.Session) -> requests.Response: def vulnerability_in_traces(vuln_type: str, agent_client: requests.Session) -> bool: time.sleep(5) traces = get_traces(agent_client) - assert traces.status_code == 200 + assert traces.status_code == 200, traces.text traces_list = json.loads(traces.text) class InnerBreakException(Exception): diff --git a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py index f0eeb1eb626..4f54bc675c3 100644 --- a/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py +++ b/tests/appsec/integrations/test_flask_entrypoint_iast_patches.py @@ -7,11 +7,19 @@ def test_ddtrace_iast_flask_patch(): import dis import io + import re import sys from tests.utils import override_env from tests.utils import override_global_config + PATTERN = r"""Disassembly of add_test: +(\s*7 0 RESUME 0 +)?\s*8 \d LOAD_GLOBAL \d \((NULL \+ )?_ddtrace_aspects\) +\s*\d+ LOAD_(ATTR|METHOD)\s+\d \(add_aspect\) +\s*\d+ LOAD_FAST 0 \(a\) +\s*\d+ LOAD_FAST 1 \(b\)""" + with override_global_config(dict(_iast_enabled=True)), override_env( dict(DD_IAST_ENABLED="true", DD_IAST_REQUEST_SAMPLING="100") ): @@ -21,10 +29,9 @@ def test_ddtrace_iast_flask_patch(): dis.dis(flask_entrypoint, file=dis_output) str_output = dis_output.getvalue() # Should have replaced the binary op with the aspect in add_test: - assert "(add_aspect)" in str_output - assert "BINARY_ADD" in str_output or "BINARY_OP" not in str_output + assert re.search(PATTERN, str_output), str_output # Should have replaced the app.run() with a pass: - assert "Disassembly of run" not in str_output + # assert "Disassembly of run" not in str_output, str_output del sys.modules["tests.appsec.iast.fixtures.entrypoint.app_main_patched"] From 639da637e4546e2f82aee8d88c55bc470d8acec8 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 16 Dec 2024 11:37:19 -0500 Subject: [PATCH 312/372] ci: wait longer for opensearch to start (#11739) --- tests/contrib/elasticsearch/test_elasticsearch.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/contrib/elasticsearch/test_elasticsearch.py b/tests/contrib/elasticsearch/test_elasticsearch.py index 92ce195c92f..b80b4486e71 100644 --- a/tests/contrib/elasticsearch/test_elasticsearch.py +++ b/tests/contrib/elasticsearch/test_elasticsearch.py @@ -41,14 +41,16 @@ def wait_for_es(host: str, port: int): - for _ in range(20): + # Wait for up to 160 seconds for ES to start. + # DEV: Elasticsearch is pretty quick, but OpenSearch can take a long time to start. + for _ in range(80): try: conn = HTTPConnection(f"{host}:{port}") conn.request("GET", "/") conn.getresponse() return except Exception: - time.sleep(1) + time.sleep(2) raise Exception(f"Could not connect to ES at {host}:{port}") From bd6c2e1d07bc5bcb218af61f5f384c23aa09fb3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:47:31 +0000 Subject: [PATCH 313/372] chore: update mariadb latest version to 1.1.11 (#11727) ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/1050efa.txt | 12 ++++++------ .riot/requirements/12c10e8.txt | 18 +++++++++--------- .riot/requirements/12cb0e7.txt | 12 ++++++------ .riot/requirements/147bedb.txt | 18 +++++++++--------- .riot/requirements/16b7aa5.txt | 10 +++++----- .riot/requirements/1bf3da5.txt | 12 ++++++------ .riot/requirements/1e0ec0b.txt | 14 +++++++------- .riot/requirements/4ed631d.txt | 12 ++++++------ .riot/requirements/769aa27.txt | 12 ++++++------ .riot/requirements/85acf6e.txt | 12 ++++++------ .riot/requirements/8a17cb2.txt | 12 ++++++------ .riot/requirements/e75aea6.txt | 16 ++++++++-------- .riot/requirements/fb50881.txt | 14 +++++++------- 13 files changed, 87 insertions(+), 87 deletions(-) diff --git a/.riot/requirements/1050efa.txt b/.riot/requirements/1050efa.txt index d69750cdf3f..df4832ccbd1 100644 --- a/.riot/requirements/1050efa.txt +++ b/.riot/requirements/1050efa.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1050efa.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/12c10e8.txt b/.riot/requirements/12c10e8.txt index abe4c79bdd0..75ea709c67a 100644 --- a/.riot/requirements/12c10e8.txt +++ b/.riot/requirements/12c10e8.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/12c10e8.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/12cb0e7.txt b/.riot/requirements/12cb0e7.txt index 95dd85db63b..303f7985e32 100644 --- a/.riot/requirements/12cb0e7.txt +++ b/.riot/requirements/12cb0e7.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/12cb0e7.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/147bedb.txt b/.riot/requirements/147bedb.txt index d128fe5aaa2..b03efd4dc82 100644 --- a/.riot/requirements/147bedb.txt +++ b/.riot/requirements/147bedb.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/147bedb.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/16b7aa5.txt b/.riot/requirements/16b7aa5.txt index e53e34a8205..f8d2399a7eb 100644 --- a/.riot/requirements/16b7aa5.txt +++ b/.riot/requirements/16b7aa5.txt @@ -8,17 +8,17 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mariadb==1.0.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/1bf3da5.txt b/.riot/requirements/1bf3da5.txt index 24b990913fb..ffd311eb163 100644 --- a/.riot/requirements/1bf3da5.txt +++ b/.riot/requirements/1bf3da5.txt @@ -8,17 +8,17 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/1e0ec0b.txt b/.riot/requirements/1e0ec0b.txt index 26f75087ae7..5f5ddcf3598 100644 --- a/.riot/requirements/1e0ec0b.txt +++ b/.riot/requirements/1e0ec0b.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1e0ec0b.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/4ed631d.txt b/.riot/requirements/4ed631d.txt index b627027f63d..a63d5635068 100644 --- a/.riot/requirements/4ed631d.txt +++ b/.riot/requirements/4ed631d.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/4ed631d.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/769aa27.txt b/.riot/requirements/769aa27.txt index be5205a4bd0..3b3c8a013dd 100644 --- a/.riot/requirements/769aa27.txt +++ b/.riot/requirements/769aa27.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/769aa27.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/85acf6e.txt b/.riot/requirements/85acf6e.txt index 0bb20810181..d5d68c47b81 100644 --- a/.riot/requirements/85acf6e.txt +++ b/.riot/requirements/85acf6e.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/85acf6e.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mariadb==1.0.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/8a17cb2.txt b/.riot/requirements/8a17cb2.txt index 3d204884507..f8916e4d431 100644 --- a/.riot/requirements/8a17cb2.txt +++ b/.riot/requirements/8a17cb2.txt @@ -8,17 +8,17 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/e75aea6.txt b/.riot/requirements/e75aea6.txt index 4d191b83adc..704e2fecf98 100644 --- a/.riot/requirements/e75aea6.txt +++ b/.riot/requirements/e75aea6.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/e75aea6.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mariadb==1.0.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/fb50881.txt b/.riot/requirements/fb50881.txt index f39cd8b01b4..6ec4fc75d44 100644 --- a/.riot/requirements/fb50881.txt +++ b/.riot/requirements/fb50881.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/fb50881.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 -mariadb==1.1.10 +mariadb==1.1.11 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 From 3828edb95d6947e095f331722465b83416ee8f48 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:49:55 +0000 Subject: [PATCH 314/372] chore: update asyncpg latest version to 0.30.0 (#11728) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/12594bd.txt | 32 ++++++++++++++++---------------- .riot/requirements/1f2ab25.txt | 30 +++++++++++++++--------------- .riot/requirements/4c87f15.txt | 23 +++++++++++------------ .riot/requirements/6ebd15f.txt | 22 +++++++++++----------- .riot/requirements/85c8e30.txt | 10 +++++----- .riot/requirements/aaf6987.txt | 24 ++++++++++++------------ .riot/requirements/b970d9a.txt | 23 +++++++++++------------ .riot/requirements/bc5cfa5.txt | 28 ++++++++++++++-------------- .riot/requirements/ca86aae.txt | 30 +++++++++++++++--------------- .riot/requirements/f7ca81b.txt | 10 +++++----- .riot/requirements/fa9267f.txt | 16 ++++++++-------- 11 files changed, 123 insertions(+), 125 deletions(-) diff --git a/.riot/requirements/12594bd.txt b/.riot/requirements/12594bd.txt index e8e2f1234f5..9311ce66a7f 100644 --- a/.riot/requirements/12594bd.txt +++ b/.riot/requirements/12594bd.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/12594bd.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/12594bd.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==7.0.1 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1f2ab25.txt b/.riot/requirements/1f2ab25.txt index eb17561c98f..ee70e55666e 100644 --- a/.riot/requirements/1f2ab25.txt +++ b/.riot/requirements/1f2ab25.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1f2ab25.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f2ab25.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==7.0.1 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/4c87f15.txt b/.riot/requirements/4c87f15.txt index e5138ff00ca..1ab11af15de 100644 --- a/.riot/requirements/4c87f15.txt +++ b/.riot/requirements/4c87f15.txt @@ -2,21 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/4c87f15.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4c87f15.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/6ebd15f.txt b/.riot/requirements/6ebd15f.txt index e15eb9136da..8665e48a87a 100644 --- a/.riot/requirements/6ebd15f.txt +++ b/.riot/requirements/6ebd15f.txt @@ -2,24 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/6ebd15f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6ebd15f.in # asyncpg==0.23.0 -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 +attrs==24.2.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.0.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.2.2 +pytest==8.3.4 pytest-asyncio==0.21.2 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/85c8e30.txt b/.riot/requirements/85c8e30.txt index e24c6ea9c30..4d03cfcf992 100644 --- a/.riot/requirements/85c8e30.txt +++ b/.riot/requirements/85c8e30.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/85c8e30.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/85c8e30.in # asyncpg==0.28.0 -attrs==23.2.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 pytest==7.4.4 -pytest-asyncio==0.21.1 +pytest-asyncio==0.21.2 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/aaf6987.txt b/.riot/requirements/aaf6987.txt index c5cf067fda4..325d23c244d 100644 --- a/.riot/requirements/aaf6987.txt +++ b/.riot/requirements/aaf6987.txt @@ -2,22 +2,22 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/aaf6987.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/aaf6987.in # asyncpg==0.24.0 -attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 +attrs==24.2.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/b970d9a.txt b/.riot/requirements/b970d9a.txt index 4c48960f797..5b6566c9768 100644 --- a/.riot/requirements/b970d9a.txt +++ b/.riot/requirements/b970d9a.txt @@ -2,21 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/b970d9a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b970d9a.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/bc5cfa5.txt b/.riot/requirements/bc5cfa5.txt index 6bb0594f42e..69e33321ec8 100644 --- a/.riot/requirements/bc5cfa5.txt +++ b/.riot/requirements/bc5cfa5.txt @@ -2,23 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/bc5cfa5.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bc5cfa5.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/ca86aae.txt b/.riot/requirements/ca86aae.txt index f37a682c7bd..be8832160e7 100644 --- a/.riot/requirements/ca86aae.txt +++ b/.riot/requirements/ca86aae.txt @@ -2,25 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/ca86aae.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ca86aae.in # -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 +async-timeout==5.0.1 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==7.0.1 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/f7ca81b.txt b/.riot/requirements/f7ca81b.txt index 8a63c4f0856..14de8077cae 100644 --- a/.riot/requirements/f7ca81b.txt +++ b/.riot/requirements/f7ca81b.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/f7ca81b.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/f7ca81b.in # asyncpg==0.28.0 -attrs==23.2.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 pytest==7.4.4 -pytest-asyncio==0.21.1 +pytest-asyncio==0.21.2 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/fa9267f.txt b/.riot/requirements/fa9267f.txt index 80c862d47e3..675a460f3bc 100644 --- a/.riot/requirements/fa9267f.txt +++ b/.riot/requirements/fa9267f.txt @@ -2,20 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/fa9267f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/fa9267f.in # -asyncpg==0.29.0 -attrs==23.2.0 -coverage[toml]==7.5.4 +asyncpg==0.30.0 +attrs==24.2.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.2.2 +pytest==8.3.4 pytest-asyncio==0.21.2 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 From 872f1af90be3b6769d531bb92b1cb677e64dea73 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:52:55 +0000 Subject: [PATCH 315/372] chore: update pyodbc latest version to 5.2.0 (#11733) Update pyodbc lockfiles and dependency package lockfiles. This performs the following updates: 1) Some pyodbc lockfiles use pyodbc `latest`. This will update pyodbc and dependencies. 2) Some pyodbc lockfiles use a pinned (non-latest) version of pyodbc, but require the `latest` version of another package. This will update all such packages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/17879d0.txt | 14 +++++++------- .riot/requirements/1810da7.txt | 10 +++++----- .riot/requirements/188a403.txt | 16 ++++++++-------- .riot/requirements/1af9cfa.txt | 12 ++++++------ .riot/requirements/1ef773e.txt | 12 ++++++------ .riot/requirements/9a81f68.txt | 18 +++++++++--------- .riot/requirements/c4dace8.txt | 12 ++++++------ .riot/requirements/ed78a8f.txt | 12 ++++++------ 8 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.riot/requirements/17879d0.txt b/.riot/requirements/17879d0.txt index 9e7c9459a2b..339e06c1336 100644 --- a/.riot/requirements/17879d0.txt +++ b/.riot/requirements/17879d0.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/17879d0.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyodbc==5.1.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pyodbc==5.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/1810da7.txt b/.riot/requirements/1810da7.txt index 4c80eec5a83..020c016edce 100644 --- a/.riot/requirements/1810da7.txt +++ b/.riot/requirements/1810da7.txt @@ -8,17 +8,17 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pyodbc==4.0.39 -pytest==8.3.2 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/188a403.txt b/.riot/requirements/188a403.txt index 62354b22cef..37541506b8d 100644 --- a/.riot/requirements/188a403.txt +++ b/.riot/requirements/188a403.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/188a403.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pyodbc==4.0.39 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1af9cfa.txt b/.riot/requirements/1af9cfa.txt index 3a796251a8b..7a5efd36134 100644 --- a/.riot/requirements/1af9cfa.txt +++ b/.riot/requirements/1af9cfa.txt @@ -5,18 +5,18 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1af9cfa.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 pyodbc==4.0.39 -pytest==8.3.2 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/1ef773e.txt b/.riot/requirements/1ef773e.txt index 16dcedbeacf..88ce7283fd9 100644 --- a/.riot/requirements/1ef773e.txt +++ b/.riot/requirements/1ef773e.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/1ef773e.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyodbc==5.1.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pyodbc==5.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/9a81f68.txt b/.riot/requirements/9a81f68.txt index de20e1d5784..83a8d9649f8 100644 --- a/.riot/requirements/9a81f68.txt +++ b/.riot/requirements/9a81f68.txt @@ -5,20 +5,20 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/9a81f68.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyodbc==5.1.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pyodbc==5.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/c4dace8.txt b/.riot/requirements/c4dace8.txt index a6044120570..b828932c4c2 100644 --- a/.riot/requirements/c4dace8.txt +++ b/.riot/requirements/c4dace8.txt @@ -8,17 +8,17 @@ attrs==24.2.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 -importlib-metadata==8.4.0 +importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyodbc==5.1.0 -pytest==8.3.2 +pyodbc==5.2.0 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/ed78a8f.txt b/.riot/requirements/ed78a8f.txt index 95a78831a4d..d9c1cb0b78f 100644 --- a/.riot/requirements/ed78a8f.txt +++ b/.riot/requirements/ed78a8f.txt @@ -5,16 +5,16 @@ # pip-compile --allow-unsafe --no-annotate .riot/requirements/ed78a8f.in # attrs==24.2.0 -coverage[toml]==7.6.1 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pyodbc==5.1.0 -pytest==8.3.2 -pytest-cov==5.0.0 +pyodbc==5.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 From 28c4466fb6a8f87c64750b0c1dcca91a3ecf39c4 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:07:16 -0800 Subject: [PATCH 316/372] chore: refactor the debugger for clarity (#11721) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 12 ++++++------ ddtrace/debugging/_function/store.py | 25 ++++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 7d4a283b26d..1c2429ba569 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -25,7 +25,7 @@ from ddtrace._trace.tracer import Tracer from ddtrace.debugging._config import di_config from ddtrace.debugging._function.discovery import FunctionDiscovery -from ddtrace.debugging._function.store import FullyNamedWrappedFunction +from ddtrace.debugging._function.store import FullyNamedContextWrappedFunction from ddtrace.debugging._function.store import FunctionStore from ddtrace.debugging._metrics import metrics from ddtrace.debugging._probe.model import FunctionLocationMixin @@ -386,7 +386,7 @@ def _probe_injection_hook(self, module: ModuleType) -> None: # Group probes by function so that we decompile each function once and # bulk-inject the probes. - probes_for_function: Dict[FullyNamedWrappedFunction, List[Probe]] = defaultdict(list) + probes_for_function: Dict[FullyNamedContextWrappedFunction, List[Probe]] = defaultdict(list) for probe in self._probe_registry.get_pending(str(origin(module))): if not isinstance(probe, LineLocationMixin): continue @@ -410,7 +410,7 @@ def _probe_injection_hook(self, module: ModuleType) -> None: log.error(message) self._probe_registry.set_error(probe, "NoFunctionsAtLine", message) continue - for function in (cast(FullyNamedWrappedFunction, _) for _ in functions): + for function in (cast(FullyNamedContextWrappedFunction, _) for _ in functions): probes_for_function[function].append(cast(LineProbe, probe)) for function, probes in probes_for_function.items(): @@ -485,14 +485,14 @@ def _eject_probes(self, probes_to_eject: List[LineProbe]) -> None: module = self.__watchdog__.get_by_origin(resolved_source) if module is not None: # The module is still loaded, so we can try to eject the hooks - probes_for_function: Dict[FullyNamedWrappedFunction, List[LineProbe]] = defaultdict(list) + probes_for_function: Dict[FullyNamedContextWrappedFunction, List[LineProbe]] = defaultdict(list) for probe in probes: if not isinstance(probe, LineLocationMixin): continue line = probe.line assert line is not None, probe # nosec functions = FunctionDiscovery.from_module(module).at_line(line) - for function in (cast(FullyNamedWrappedFunction, _) for _ in functions): + for function in (cast(FullyNamedContextWrappedFunction, _) for _ in functions): probes_for_function[function].append(probe) for function, ps in probes_for_function.items(): @@ -599,7 +599,7 @@ def _unwrap_functions(self, probes: List[FunctionProbe]) -> None: context = cast(DebuggerWrappingContext, DebuggerWrappingContext.extract(function)) context.remove_probe(probe) if not context.has_probes(): - self._function_store.unwrap(cast(FullyNamedWrappedFunction, function)) + self._function_store.unwrap(cast(FullyNamedContextWrappedFunction, function)) log.debug("Unwrapped %r", registered_probe) else: log.error("Attempted to unwrap %r, but no wrapper found", registered_probe) diff --git a/ddtrace/debugging/_function/store.py b/ddtrace/debugging/_function/store.py index 9a17aae3b91..e11c75070a2 100644 --- a/ddtrace/debugging/_function/store.py +++ b/ddtrace/debugging/_function/store.py @@ -13,14 +13,14 @@ from ddtrace.internal.injection import HookType from ddtrace.internal.injection import eject_hooks from ddtrace.internal.injection import inject_hooks -from ddtrace.internal.wrapping import WrappedFunction +from ddtrace.internal.wrapping.context import ContextWrappedFunction from ddtrace.internal.wrapping.context import WrappingContext WrapperType = Callable[[FunctionType, Any, Any, Any], Any] -class FullyNamedWrappedFunction(FullyNamed, WrappedFunction): +class FullyNamedContextWrappedFunction(FullyNamed, ContextWrappedFunction): """A fully named wrapper function.""" @@ -54,17 +54,17 @@ def _store(self, function: FunctionType) -> None: if function not in self._code_map: self._code_map[function] = function.__code__ - def inject_hooks(self, function: FullyNamedWrappedFunction, hooks: List[HookInfoType]) -> Set[str]: + def inject_hooks(self, function: FullyNamedContextWrappedFunction, hooks: List[HookInfoType]) -> Set[str]: """Bulk-inject hooks into a function. Returns the set of probe IDs for those probes that failed to inject. """ try: - return self.inject_hooks(cast(FullyNamedWrappedFunction, function.__dd_wrapped__), hooks) + f = cast(FunctionType, cast(FullyNamedContextWrappedFunction, function.__dd_context_wrapped__.__wrapped__)) # type: ignore[union-attr] except AttributeError: f = cast(FunctionType, function) - self._store(f) - return {p.probe_id for _, _, p in inject_hooks(f, hooks)} + self._store(f) + return {p.probe_id for _, _, p in inject_hooks(f, hooks)} def eject_hooks(self, function: FunctionType, hooks: List[HookInfoType]) -> Set[str]: """Bulk-eject hooks from a function. @@ -72,15 +72,14 @@ def eject_hooks(self, function: FunctionType, hooks: List[HookInfoType]) -> Set[ Returns the set of probe IDs for those probes that failed to eject. """ try: - wrapped = cast(FullyNamedWrappedFunction, function).__dd_wrapped__ + f = cast(FullyNamedContextWrappedFunction, function).__dd_context_wrapped__.__wrapped__ # type: ignore[union-attr] except AttributeError: # Not a wrapped function so we can actually eject from it - return {p.probe_id for _, _, p in eject_hooks(function, hooks)} - else: - # Try on the wrapped function. - return self.eject_hooks(cast(FunctionType, wrapped), hooks) + f = function - def inject_hook(self, function: FullyNamedWrappedFunction, hook: HookType, line: int, arg: Any) -> bool: + return {p.probe_id for _, _, p in eject_hooks(cast(FunctionType, f), hooks)} + + def inject_hook(self, function: FullyNamedContextWrappedFunction, hook: HookType, line: int, arg: Any) -> bool: """Inject a hook into a function.""" return not not self.inject_hooks(function, [(hook, line, arg)]) @@ -94,7 +93,7 @@ def wrap(self, function: FunctionType, wrapping_context: WrappingContext) -> Non self._wrapper_map[function] = wrapping_context wrapping_context.wrap() - def unwrap(self, function: FullyNamedWrappedFunction) -> None: + def unwrap(self, function: FullyNamedContextWrappedFunction) -> None: """Unwrap a hook around a wrapped function.""" self._wrapper_map.pop(cast(FunctionType, function)).unwrap() From 56bdd61173b2e66fc884362b88db95834064cebe Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Mon, 16 Dec 2024 16:22:57 -0500 Subject: [PATCH 317/372] fix(telemetry): avoids stopping periodic threads in telemetry writer atexit hooks (#11708) Resolves: https://github.com/DataDog/dd-trace-py/issues/11622 ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/telemetry/writer.py | 26 +++++++------------ .../telemetry-deadlocks-ea3f457ab0611c8b.yaml | 4 +++ 2 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/telemetry-deadlocks-ea3f457ab0611c8b.yaml diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 63e672e24dd..71de6b03907 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -181,7 +181,6 @@ def __init__(self, is_periodic=True, agentless=None): self._forked = False # type: bool self._events_queue = [] # type: List[Dict] self._configuration_queue = {} # type: Dict[str, Dict] - self._lock = forksafe.Lock() # type: forksafe.ResetObject self._imported_dependencies: Dict[str, str] = dict() self._product_enablement = {product.value: False for product in TELEMETRY_APM_PRODUCT} self._send_product_change_updates = False @@ -245,10 +244,6 @@ def disable(self): """ self._enabled = False self.reset_queues() - if self._is_running(): - self.stop() - else: - self.status = ServiceStatus.STOPPED def enable_agentless_client(self, enabled=True): # type: (bool) -> None @@ -297,7 +292,7 @@ def add_integration(self, integration_name, patched, auto_patched=None, error_ms :param bool auto_enabled: True if module is enabled in _monkey.PATCH_MODULES """ # Integrations can be patched before the telemetry writer is enabled. - with self._lock: + with self._service_lock: if integration_name not in self._integrations_queue: self._integrations_queue[integration_name] = {"name": integration_name} @@ -382,20 +377,20 @@ def _app_integrations_changed_event(self, integrations): def _flush_integrations_queue(self): # type: () -> List[Dict] """Flushes and returns a list of all queued integrations""" - with self._lock: + with self._service_lock: integrations = list(self._integrations_queue.values()) self._integrations_queue = dict() return integrations def _flush_new_imported_dependencies(self) -> Set[str]: - with self._lock: + with self._service_lock: new_deps = modules.get_newly_imported_modules() return new_deps def _flush_configuration_queue(self): # type: () -> List[Dict] """Flushes and returns a list of all queued configurations""" - with self._lock: + with self._service_lock: configurations = list(self._configuration_queue.values()) self._configuration_queue = {} return configurations @@ -414,7 +409,7 @@ def _app_dependencies_loaded_event(self, newly_imported_deps: List[str]): if not _TelemetryConfig.DEPENDENCY_COLLECTION or not self._enabled: return - with self._lock: + with self._service_lock: packages = update_imported_dependencies(self._imported_dependencies, newly_imported_deps) if packages: @@ -451,7 +446,7 @@ def product_activated(self, product, enabled): self._send_product_change_updates = True def remove_configuration(self, configuration_name): - with self._lock: + with self._service_lock: del self._configuration_queue[configuration_name] def add_configuration(self, configuration_name, configuration_value, origin="unknown"): @@ -465,7 +460,7 @@ def add_configuration(self, configuration_name, configuration_value, origin="unk # convert unsupported types to strings configuration_value = str(configuration_value) - with self._lock: + with self._service_lock: self._configuration_queue[configuration_name] = { "name": configuration_name, "origin": origin, @@ -475,7 +470,7 @@ def add_configuration(self, configuration_name, configuration_value, origin="unk def add_configurations(self, configuration_list): # type: (List[Tuple[str, Union[bool, float, str], str]]) -> None """Creates and queues a list of configurations""" - with self._lock: + with self._service_lock: for name, value, _origin in configuration_list: self._configuration_queue[name] = { "name": name, @@ -566,7 +561,7 @@ def add_distribution_metric(self, namespace, name, value=1.0, tags=None): def _flush_log_metrics(self): # type () -> Set[Metric] - with self._lock: + with self._service_lock: log_metrics = self._logs self._logs = set() return log_metrics @@ -652,7 +647,7 @@ def reset_queues(self): def _flush_events_queue(self): # type: () -> List[Dict] """Flushes and returns a list of all telemtery event""" - with self._lock: + with self._service_lock: events = self._events_queue self._events_queue = [] return events @@ -668,7 +663,6 @@ def _fork_writer(self): if self._is_running(): self.stop(join=False) - # Enable writer service in child process to avoid interpreter shutdown # error in Python 3.12 self.enable() diff --git a/releasenotes/notes/telemetry-deadlocks-ea3f457ab0611c8b.yaml b/releasenotes/notes/telemetry-deadlocks-ea3f457ab0611c8b.yaml new file mode 100644 index 00000000000..1fe2739767d --- /dev/null +++ b/releasenotes/notes/telemetry-deadlocks-ea3f457ab0611c8b.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + library: Resolves deadlocks that could occur when sending instrumentation telemetry data after an unhandled exception is raised. From 62766b047491e76b1fd41647c62d8f6b45e9b9aa Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:43:57 -0500 Subject: [PATCH 318/372] chore(llmobs): ensure propagated parent IDs are still using span tags (#11745) This PR makes a small revert to #11543 where accessing propagated parent IDs (for distributed tracing) were unwittingly changed to access via the span store object, even though automatic context propagation results are always added to the span as tags (not the store). While all other LLMObs SDK data is added/accessed via the span store, `_dd.p.llmobs_parent_id` is automatically added by the tracer internals so we'll continue using this for now until our overall context management solution removes this problem entirely. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_integrations/base.py | 2 +- ddtrace/llmobs/_integrations/bedrock.py | 2 +- ddtrace/llmobs/_llmobs.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py index 709e72f3a26..a6968ce0d83 100644 --- a/ddtrace/llmobs/_integrations/base.py +++ b/ddtrace/llmobs/_integrations/base.py @@ -133,7 +133,7 @@ def trace(self, pin: Pin, operation_id: str, submit_to_llmobs: bool = False, **k # The LLMObs parent ID tag is not set at span start time. We need to manually set the parent ID tag now # in these cases to avoid conflicting with the later propagated tags. parent_id = _get_llmobs_parent_id(span) or "undefined" - span.set_tag_str(PARENT_ID_KEY, str(parent_id)) + span._set_ctx_item(PARENT_ID_KEY, str(parent_id)) return span @classmethod diff --git a/ddtrace/llmobs/_integrations/bedrock.py b/ddtrace/llmobs/_integrations/bedrock.py index bf8b020ebea..d2d57b50ed3 100644 --- a/ddtrace/llmobs/_integrations/bedrock.py +++ b/ddtrace/llmobs/_integrations/bedrock.py @@ -36,7 +36,7 @@ def _llmobs_set_tags( operation: str = "", ) -> None: """Extract prompt/response tags from a completion and set them as temporary "_ml_obs.*" tags.""" - if span._get_ctx_item(PROPAGATED_PARENT_ID_KEY) is None: + if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: parent_id = _get_llmobs_parent_id(span) or "undefined" span._set_ctx_item(PARENT_ID_KEY, parent_id) parameters = {} diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 867edbdca4f..49815151118 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -410,7 +410,7 @@ def _start_span( if ml_app is None: ml_app = _get_ml_app(span) span._set_ctx_item(ML_APP, ml_app) - if span._get_ctx_item(PROPAGATED_PARENT_ID_KEY) is None: + if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: # For non-distributed traces or spans in the first service of a distributed trace, # The LLMObs parent ID tag is not set at span start time. We need to manually set the parent ID tag now # in these cases to avoid conflicting with the later propagated tags. From 6de35c8638ce9da853d51af53d283822c7b72682 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 16 Dec 2024 17:06:28 -0500 Subject: [PATCH 319/372] ci: add clearer breakdown of test stages and dependencies between jobs (#11712) --- .gitlab/tests.yml | 11 +++++++---- scripts/gen_gitlab_config.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 83a5d4231b8..4495c6fa6a6 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -1,5 +1,7 @@ stages: - - tests + - precheck + - hatch + - riot variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s @@ -22,7 +24,7 @@ variables: .test_base_hatch: extends: .testrunner - stage: tests + stage: hatch # Hatch doesn't use pre-built wheels or venvs so we can start them right away needs: [] parallel: 4 @@ -57,7 +59,7 @@ variables: build_base_venvs: extends: .testrunner - stage: tests + stage: riot parallel: matrix: - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] @@ -76,9 +78,10 @@ build_base_venvs: - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* - ddtrace/internal/datadog/profiling/test/test_* +# Do not define a `needs:` in order to depend on the whole `precheck` stage .test_base_riot: extends: .testrunner - stage: tests + stage: riot needs: [ build_base_venvs ] parallel: 4 services: diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 2b139ce798d..c868b0f1c86 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -114,7 +114,7 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: with TESTS_GEN.open("a") as f: print(f'"{name}":', file=f) print(" extends: .testrunner", file=f) - print(" stage: tests", file=f) + print(" stage: precheck", file=f) print(" needs: []", file=f) print(" script:", file=f) print(f" - {command}", file=f) From 05302eb92d28ea1f9277aabdd5067babea65a455 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:44:36 +0000 Subject: [PATCH 320/372] chore(ci_visibility): add support for pytest_bdd to v2 plugin (#11753) This adds support for the `pytest-bdd` plugin to the new version of the `pytest` plugin, bypassing the `ddtrace.pytest_bdd` plugin and instantiating a "subplugin" on demand when `pytest-bdd` is detected and `ddtrace` is enabled. To support the way the legacy `pytest-bdd` plugin worked, new functionality is added: - `InternalTestSession.get_tracer()` to have access to the tracer used by Test Visibility - `InternalTest.overwrite_attributes()` to allow overwriting attributes that have already been set (or in the case of the `suite` name, that are automatically set based on test hierarchy) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_plugin_v2.py | 6 + .../contrib/pytest/_pytest_bdd_subplugin.py | 110 ++++++++++++++++++ ddtrace/contrib/pytest_bdd/plugin.py | 17 ++- ddtrace/internal/ci_visibility/api/_test.py | 22 ++++ ddtrace/internal/ci_visibility/recorder.py | 21 ++++ ddtrace/internal/test_visibility/api.py | 40 ++++++- riotfile.py | 27 ++++- tests/contrib/pytest_bdd/test_pytest_bdd.py | 51 +------- 8 files changed, 238 insertions(+), 56 deletions(-) create mode 100644 ddtrace/contrib/pytest/_pytest_bdd_subplugin.py diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index e51739ccee9..f1da8d2db11 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -196,6 +196,12 @@ def pytest_configure(config: pytest_Config) -> None: enable_test_visibility(config=dd_config.pytest) if _is_pytest_cov_enabled(config): patch_coverage() + + # pytest-bdd plugin support + if config.pluginmanager.hasplugin("pytest-bdd"): + from ddtrace.contrib.pytest._pytest_bdd_subplugin import _PytestBddSubPlugin + + config.pluginmanager.register(_PytestBddSubPlugin(), "_datadog-pytest-bdd") else: # If the pytest ddtrace plugin is not enabled, we should disable CI Visibility, as it was enabled during # pytest_load_initial_conftests diff --git a/ddtrace/contrib/pytest/_pytest_bdd_subplugin.py b/ddtrace/contrib/pytest/_pytest_bdd_subplugin.py new file mode 100644 index 00000000000..7c964af3d59 --- /dev/null +++ b/ddtrace/contrib/pytest/_pytest_bdd_subplugin.py @@ -0,0 +1,110 @@ +"""Provides functionality to support the pytest-bdd plugin as part of the ddtrace integration + +NOTE: This replaces the previous ddtrace.pytest_bdd plugin. + +This plugin mainly modifies the names of the test, its suite, and parameters. It does not, however modify the tests' +suite from the perspective of Test Visibility data. + +The plugin is only instantiated and added if the pytest-bdd plugin itself is installed and enabled, because the hook +implementations will cause errors unless the hookspecs are added by the original plugin. +""" +from pathlib import Path +import sys + +import pytest + +from ddtrace.contrib.pytest._utils import _get_test_id_from_item +from ddtrace.contrib.pytest_bdd import get_version +from ddtrace.contrib.pytest_bdd._plugin import _extract_span +from ddtrace.contrib.pytest_bdd._plugin import _get_step_func_args_json +from ddtrace.contrib.pytest_bdd._plugin import _store_span +from ddtrace.contrib.pytest_bdd.constants import FRAMEWORK +from ddtrace.contrib.pytest_bdd.constants import STEP_KIND +from ddtrace.ext import test +from ddtrace.internal.logger import get_logger +from ddtrace.internal.test_visibility.api import InternalTest +from ddtrace.internal.test_visibility.api import InternalTestSession + + +log = get_logger(__name__) + + +def _get_workspace_relative_path(feature_path_str: str) -> Path: + feature_path = Path(feature_path_str).resolve() + workspace_path = InternalTestSession.get_workspace_path() + if workspace_path: + try: + return feature_path.relative_to(workspace_path) + except ValueError: # noqa: E722 + log.debug("Feature path %s is not relative to workspace path %s", feature_path, workspace_path) + return feature_path + + +class _PytestBddSubPlugin: + def __init__(self): + self.framework_version = get_version() + + @staticmethod + @pytest.hookimpl(tryfirst=True) + def pytest_bdd_before_scenario(request, feature, scenario): + test_id = _get_test_id_from_item(request.node) + feature_path = _get_workspace_relative_path(scenario.feature.filename) + codeowners = InternalTestSession.get_path_codeowners(feature_path) + + InternalTest.overwrite_attributes( + test_id, name=scenario.name, suite_name=str(feature_path), codeowners=codeowners + ) + + @pytest.hookimpl(tryfirst=True) + def pytest_bdd_before_step(self, request, feature, scenario, step, step_func): + feature_test_id = _get_test_id_from_item(request.node) + + feature_span = InternalTest.get_span(feature_test_id) + + tracer = InternalTestSession.get_tracer() + if tracer is None: + return + + span = tracer.start_span( + step.type, + resource=step.name, + span_type=STEP_KIND, + child_of=feature_span, + activate=True, + ) + span.set_tag_str("component", "pytest_bdd") + + span.set_tag(test.FRAMEWORK, FRAMEWORK) + span.set_tag(test.FRAMEWORK_VERSION, self.framework_version) + + feature_path = _get_workspace_relative_path(scenario.feature.filename) + + span.set_tag(test.FILE, str(feature_path)) + span.set_tag(test.CODEOWNERS, InternalTestSession.get_path_codeowners(feature_path)) + + _store_span(step_func, span) + + @staticmethod + @pytest.hookimpl(trylast=True) + def pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args): + span = _extract_span(step_func) + if span is not None: + step_func_args_json = _get_step_func_args_json(step, step_func, step_func_args) + if step_func_args: + span.set_tag(test.PARAMETERS, step_func_args_json) + span.finish() + + @staticmethod + def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception): + span = _extract_span(step_func) + if span is not None: + if hasattr(exception, "__traceback__"): + tb = exception.__traceback__ + else: + # PY2 compatibility workaround + _, _, tb = sys.exc_info() + step_func_args_json = _get_step_func_args_json(step, step_func, step_func_args) + if step_func_args: + span.set_tag(test.PARAMETERS, step_func_args_json) + span.set_exc_info(type(exception), exception, tb) + span.finish() diff --git a/ddtrace/contrib/pytest_bdd/plugin.py b/ddtrace/contrib/pytest_bdd/plugin.py index 68da4a3a3c5..1dc714c89c5 100644 --- a/ddtrace/contrib/pytest_bdd/plugin.py +++ b/ddtrace/contrib/pytest_bdd/plugin.py @@ -1,9 +1,20 @@ +from ddtrace import DDTraceDeprecationWarning +from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 from ddtrace.contrib.pytest.plugin import is_enabled as is_ddtrace_enabled +from ddtrace.vendor.debtcollector import deprecate def pytest_configure(config): if config.pluginmanager.hasplugin("pytest-bdd") and config.pluginmanager.hasplugin("ddtrace"): - if is_ddtrace_enabled(config): - from ._plugin import _PytestBddPlugin + if not _USE_PLUGIN_V2: + if is_ddtrace_enabled(config): + from ._plugin import _PytestBddPlugin - config.pluginmanager.register(_PytestBddPlugin(), "_datadog-pytest-bdd") + deprecate( + "the ddtrace.pytest_bdd plugin is deprecated", + message="it will be integrated with the main pytest ddtrace plugin", + removal_version="3.0.0", + category=DDTraceDeprecationWarning, + ) + + config.pluginmanager.register(_PytestBddPlugin(), "_datadog-pytest-bdd") diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index 7a61473ff92..c63d9753eb8 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -85,6 +85,9 @@ def __init__( self._is_benchmark = False self._benchmark_duration_data: Optional[BenchmarkDurationData] = None + # Some parameters can be overwritten: + self._overwritten_suite_name: Optional[str] = None + def __repr__(self) -> str: suite_name = self.parent.name if self.parent is not None else "none" module_name = self.parent.parent.name if self.parent is not None and self.parent.parent is not None else "none" @@ -102,6 +105,9 @@ def _set_item_tags(self) -> None: if self._is_benchmark: self.set_tag(test.TYPE, BENCHMARK) + if self._overwritten_suite_name is not None: + self.set_tag(test.SUITE, self._overwritten_suite_name) + def _set_efd_tags(self) -> None: if self._efd_is_retry: self.set_tag(TEST_IS_RETRY, self._efd_is_retry) @@ -202,6 +208,22 @@ def finish_itr_skipped(self) -> None: self.mark_itr_skipped() self.finish_test(TestStatus.SKIP) + def overwrite_attributes( + self, + name: Optional[str] = None, + suite_name: Optional[str] = None, + parameters: Optional[str] = None, + codeowners: Optional[List[str]] = None, + ) -> None: + if name is not None: + self.name = name + if suite_name is not None: + self._overwritten_suite_name = suite_name + if parameters is not None: + self.set_parameters(parameters) + if codeowners is not None: + self._codeowners = codeowners + def add_coverage_data(self, coverage_data: Dict[Path, CoverageLines]) -> None: self._coverage_data.add_covered_files(coverage_data) diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index b306a1e7912..0046c21be15 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -79,6 +79,7 @@ from ddtrace.internal.test_visibility._efd_mixins import EFDTestStatus from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from ddtrace.internal.test_visibility._itr_mixins import ITRMixin +from ddtrace.internal.test_visibility.api import InternalTest from ddtrace.internal.test_visibility.coverage_lines import CoverageLines from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.http import verify_url @@ -1008,6 +1009,12 @@ def _on_session_get_codeowners() -> Optional[Codeowners]: return CIVisibility.get_codeowners() +@_requires_civisibility_enabled +def _on_session_get_tracer() -> Optional[Tracer]: + log.debug("Getting tracer") + return CIVisibility.get_tracer() + + @_requires_civisibility_enabled def _on_session_is_atr_enabled() -> bool: log.debug("Getting Auto Test Retries enabled") @@ -1041,6 +1048,7 @@ def _register_session_handlers(): core.on("test_visibility.session.start", _on_start_session) core.on("test_visibility.session.finish", _on_finish_session) core.on("test_visibility.session.get_codeowners", _on_session_get_codeowners, "codeowners") + core.on("test_visibility.session.get_tracer", _on_session_get_tracer, "tracer") core.on("test_visibility.session.get_path_codeowners", _on_session_get_path_codeowners, "path_codeowners") core.on("test_visibility.session.get_workspace_path", _on_session_get_workspace_path, "workspace_path") core.on("test_visibility.session.is_atr_enabled", _on_session_is_atr_enabled, "is_atr_enabled") @@ -1191,6 +1199,18 @@ def _on_set_benchmark_data(set_benchmark_data_args: BenchmarkTestMixin.SetBenchm CIVisibility.get_test_by_id(item_id).set_benchmark_data(data, is_benchmark) +@_requires_civisibility_enabled +def _on_test_overwrite_attributes(overwrite_attribute_args: InternalTest.OverwriteAttributesArgs): + item_id = overwrite_attribute_args.test_id + name = overwrite_attribute_args.name + suite_name = overwrite_attribute_args.suite_name + parameters = overwrite_attribute_args.parameters + codeowners = overwrite_attribute_args.codeowners + + log.debug("Handling overwrite attributes: %s", overwrite_attribute_args) + CIVisibility.get_test_by_id(item_id).overwrite_attributes(name, suite_name, parameters, codeowners) + + def _register_test_handlers(): log.debug("Registering test handlers") core.on("test_visibility.test.discover", _on_discover_test) @@ -1199,6 +1219,7 @@ def _register_test_handlers(): core.on("test_visibility.test.finish", _on_finish_test) core.on("test_visibility.test.set_parameters", _on_set_test_parameters) core.on("test_visibility.test.set_benchmark_data", _on_set_benchmark_data) + core.on("test_visibility.test.overwrite_attributes", _on_test_overwrite_attributes) @_requires_civisibility_enabled diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index d66dbcc32c7..84f559a4701 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -3,6 +3,7 @@ from typing import NamedTuple from ddtrace import Span +from ddtrace import Tracer from ddtrace.ext.test_visibility import api as ext_api from ddtrace.ext.test_visibility._test_visibility_base import TestSessionId from ddtrace.ext.test_visibility._utils import _catch_and_log_exceptions @@ -73,7 +74,15 @@ def get_codeowners() -> t.Optional[_Codeowners]: @staticmethod @_catch_and_log_exceptions - def get_workspace_path() -> Path: + def get_tracer() -> t.Optional[Tracer]: + log.debug("Getting test session tracer") + tracer: t.Optional[Tracer] = core.dispatch_with_results("test_visibility.session.get_tracer").tracer.value + log.debug("Got test session tracer: %s", tracer) + return tracer + + @staticmethod + @_catch_and_log_exceptions + def get_workspace_path() -> t.Optional[Path]: log.debug("Getting session workspace path") workspace_path: Path = core.dispatch_with_results( @@ -165,3 +174,32 @@ def is_new_test(item_id: InternalTestId) -> bool: is_new = bool(core.dispatch_with_results("test_visibility.test.is_new", (item_id,)).is_new.value) log.debug("Test %s is new: %s", item_id, is_new) return is_new + + class OverwriteAttributesArgs(NamedTuple): + test_id: InternalTestId + name: t.Optional[str] = None + suite_name: t.Optional[str] = None + parameters: t.Optional[str] = None + codeowners: t.Optional[t.List[str]] = None + + @staticmethod + @_catch_and_log_exceptions + def overwrite_attributes( + item_id: InternalTestId, + name: t.Optional[str] = None, + suite_name: t.Optional[str] = None, + parameters: t.Optional[str] = None, + codeowners: t.Optional[t.List[str]] = None, + ): + log.debug( + "Overwriting attributes for test %s: name=%s" ", suite_name=%s" ", parameters=%s" ", codeowners=%s", + item_id, + name, + suite_name, + parameters, + codeowners, + ) + core.dispatch( + "test_visibility.test.overwrite_attributes", + (InternalTest.OverwriteAttributesArgs(item_id, name, suite_name, parameters, codeowners),), + ) diff --git a/riotfile.py b/riotfile.py index 653023da524..567a5f65d66 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1695,9 +1695,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "more_itertools": "<8.11.0", "pytest-randomly": latest, }, - env={ - "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", - }, venvs=[ Venv( pys=select_pys(min_version="3.7", max_version="3.9"), @@ -1708,6 +1705,18 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ">=6.0,<6.1", ] }, + venvs=[ + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + }, + ), + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + }, + ), + ], ), Venv( pys=select_pys(min_version="3.10"), @@ -1718,6 +1727,18 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ">=6.0,<6.1", ] }, + venvs=[ + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + }, + ), + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + }, + ), + ], ), ], ), diff --git a/tests/contrib/pytest_bdd/test_pytest_bdd.py b/tests/contrib/pytest_bdd/test_pytest_bdd.py index e8e4e14172b..edf3ab90454 100644 --- a/tests/contrib/pytest_bdd/test_pytest_bdd.py +++ b/tests/contrib/pytest_bdd/test_pytest_bdd.py @@ -1,22 +1,12 @@ import json import os -from unittest import mock -import pytest - -import ddtrace from ddtrace.constants import ERROR_MSG -from ddtrace.contrib.pytest.plugin import is_enabled from ddtrace.contrib.pytest_bdd._plugin import _get_step_func_args_json from ddtrace.contrib.pytest_bdd._plugin import get_version from ddtrace.ext import test -from ddtrace.internal.ci_visibility import CIVisibility -from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings -from tests.ci_visibility.util import _patch_dummy_writer from tests.contrib.patch import emit_integration_and_version_to_test_agent -from tests.utils import DummyCIVisibilityWriter -from tests.utils import TracerTestCase -from tests.utils import override_env +from tests.contrib.pytest.test_pytest import PytestTestCaseBase _SIMPLE_SCENARIO = """ @@ -28,44 +18,7 @@ """ -class TestPytest(TracerTestCase): - @pytest.fixture(autouse=True) - def fixtures(self, testdir, monkeypatch): - self.testdir = testdir - self.monkeypatch = monkeypatch - - @pytest.fixture(autouse=True) - def _dummy_check_enabled_features(self): - """By default, assume that _check_enabled_features() returns an ITR-disabled response. - - Tests that need a different response should re-patch the CIVisibility object. - """ - with mock.patch( - "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", - return_value=TestVisibilityAPISettings(False, False, False, False), - ): - yield - - def inline_run(self, *args): - """Execute test script with test tracer.""" - - class CIVisibilityPlugin: - @staticmethod - def pytest_configure(config): - if is_enabled(config): - with _patch_dummy_writer(): - assert CIVisibility.enabled - CIVisibility.disable() - CIVisibility.enable(tracer=self.tracer, config=ddtrace.config.pytest) - - with override_env(dict(DD_API_KEY="foobar.baz")): - self.tracer.configure(writer=DummyCIVisibilityWriter("https://citestcycle-intake.banana")) - return self.testdir.inline_run(*args, plugins=[CIVisibilityPlugin()]) - - def subprocess_run(self, *args): - """Execute test script with test tracer.""" - return self.testdir.runpytest_subprocess(*args) - +class TestPytest(PytestTestCaseBase): def test_and_emit_get_version(self): version = get_version() assert isinstance(version, str) From a4f0e380a459542795f2d881895f18765211893b Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 17 Dec 2024 12:57:28 +0100 Subject: [PATCH 321/372] fix(iast): check context is enable in request and builtins patched funcions (#11752) This fix resolves an issue where AppSec was using a patched request and builtins functions, creating telemetry errors. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_common_module_patches.py | 16 ++++++++++++---- ...ast-propagation-error-2-ba4a998133269a7c.yaml | 5 +++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/iast-fix-iast-propagation-error-2-ba4a998133269a7c.yaml diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index a5ab2d1533d..e7ce12d13e9 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -60,8 +60,10 @@ def wrapped_read_F3E51D71B4EC16EF(original_read_callable, instance, args, kwargs """ wrapper for _io.BytesIO and _io.StringIO read function """ + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + result = original_read_callable(*args, **kwargs) - if asm_config._iast_enabled: + if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges @@ -87,7 +89,9 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs """ wrapper for open file function """ - if asm_config._iast_enabled: + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + + if asm_config._iast_enabled and is_iast_request_enabled(): try: from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal @@ -176,7 +180,9 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, wrapper for third party requests.request function https://requests.readthedocs.io """ - if asm_config._iast_enabled: + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + + if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast.taint_sinks.ssrf import _iast_report_ssrf _iast_report_ssrf(original_request_callable, *args, **kwargs) @@ -216,7 +222,9 @@ def wrapped_system_5542593D237084A7(original_command_callable, instance, args, k """ command = args[0] if args else kwargs.get("command", None) if command is not None: - if asm_config._iast_enabled: + from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled + + if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast.taint_sinks.command_injection import _iast_report_cmdi _iast_report_cmdi(command) diff --git a/releasenotes/notes/iast-fix-iast-propagation-error-2-ba4a998133269a7c.yaml b/releasenotes/notes/iast-fix-iast-propagation-error-2-ba4a998133269a7c.yaml new file mode 100644 index 00000000000..4918edb17a7 --- /dev/null +++ b/releasenotes/notes/iast-fix-iast-propagation-error-2-ba4a998133269a7c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + ASM: This fix resolves an issue where AppSec was using a patched request and builtins functions, + creating telemetry errors. \ No newline at end of file From 7b77598f43fea1a408dc162c18b25357b8c1191f Mon Sep 17 00:00:00 2001 From: Dylan Burns Date: Tue, 17 Dec 2024 11:49:00 -0500 Subject: [PATCH 322/372] fix(datastreams): botocore - log warning on kinesis stream metadata not found (#11647) For Data Streams Monitoring with AWS Kinesis, the map on the DSM page will break if `StreamARN` isn't provided to the Kinesis `get_records` call. This is because dd-trace-py requires the `StreamARN` when generating a consume checkpoint for an in-edge in the data streams map. If there's no consume checkpoint, the data streams map incorrectly renders as two graphs instead of one connected graph. However, the Kinesis `get_records` API lists `StreamARN` as an optional parameter (see [docs](https://boto3.amazonaws.com/v1/documentation/api/1.35.9/reference/services/kinesis/client/get_records.html)). If it isn't provided, dd-trace-py only outputs a debug-level log, and the DSM map is rendered incorrectly. This PR ensures dd-trace-py outputs a warning that is more visible to the developer. This PR can be closed if the current state of error handling is acceptable, since I don't know if there are restrictions on logging warnings for this repo. The warning helps to debug the broken DSM map, which is time-consuming to debug otherwise. No testing updates are required since there isn't a unit test file for this specific module. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/datastreams/botocore.py | 8 ++++++-- ...inesis-stream-metadata-not-found-a921cabed5d4397e.yaml | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/log-warning-on-kinesis-stream-metadata-not-found-a921cabed5d4397e.yaml diff --git a/ddtrace/internal/datastreams/botocore.py b/ddtrace/internal/datastreams/botocore.py index ec004f1ff9a..aeafa70ec2e 100644 --- a/ddtrace/internal/datastreams/botocore.py +++ b/ddtrace/internal/datastreams/botocore.py @@ -187,6 +187,10 @@ def handle_sqs_receive(_, params, result, *args): log.debug("Error receiving SQS message with data streams monitoring enabled", exc_info=True) +class StreamMetadataNotFound(Exception): + pass + + def record_data_streams_path_for_kinesis_stream(params, time_estimate, context_json, record): from . import data_streams_processor as processor @@ -194,7 +198,7 @@ def record_data_streams_path_for_kinesis_stream(params, time_estimate, context_j if not stream: log.debug("Unable to determine StreamARN and/or StreamName for request with params: ", params) - return + raise StreamMetadataNotFound() payload_size = calculate_kinesis_payload_size(record) ctx = DsmPathwayCodec.decode(context_json, processor()) @@ -210,7 +214,7 @@ def handle_kinesis_receive(_, params, time_estimate, context_json, record, *args try: record_data_streams_path_for_kinesis_stream(params, time_estimate, context_json, record) except Exception: - log.debug("Failed to report data streams monitoring info for kinesis", exc_info=True) + log.warning("Failed to report data streams monitoring info for kinesis", exc_info=True) if config._data_streams_enabled: diff --git a/releasenotes/notes/log-warning-on-kinesis-stream-metadata-not-found-a921cabed5d4397e.yaml b/releasenotes/notes/log-warning-on-kinesis-stream-metadata-not-found-a921cabed5d4397e.yaml new file mode 100644 index 00000000000..ed0dda53ea8 --- /dev/null +++ b/releasenotes/notes/log-warning-on-kinesis-stream-metadata-not-found-a921cabed5d4397e.yaml @@ -0,0 +1,3 @@ +fixes: + - | + datastreams: Logs at warning level for Kinesis errors that break the Data Streams Monitoring map. From 29ccfdc897d8cb239396f70aea55dcb9df7cb8e0 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 17 Dec 2024 16:58:53 +0000 Subject: [PATCH 323/372] chore(di): cache function code pair resolution (#11757) We make sure to cache the result of the function code pair resolution for subsequent calls. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_function/discovery.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ddtrace/debugging/_function/discovery.py b/ddtrace/debugging/_function/discovery.py index e7d37246f5f..6a259f0f93c 100644 --- a/ddtrace/debugging/_function/discovery.py +++ b/ddtrace/debugging/_function/discovery.py @@ -159,7 +159,8 @@ def resolve(self) -> FullyNamedFunction: msg = f"Multiple functions found for code object {code}" raise ValueError(msg) - f = cast(FullyNamedFunction, functions[0]) + self.function = _f = functions[0] + f = cast(FullyNamedFunction, _f) f.__fullname__ = f"{f.__module__}.{f.__qualname__}" return f @@ -254,6 +255,7 @@ def __init__(self, module: ModuleType) -> None: if hasattr(module, "__dd_code__"): for code in module.__dd_code__: fcp = _FunctionCodePair(code=code) + if PYTHON_VERSION_INFO >= (3, 11): # From this version of Python we can derive the qualified # name of the function directly from the code object. @@ -261,8 +263,9 @@ def __init__(self, module: ModuleType) -> None: self._fullname_index[fullname] = fcp else: self._name_index[code.co_name].append(fcp) + for lineno in linenos(code): - self[lineno].append(_FunctionCodePair(code=code)) + self[lineno].append(fcp) else: # If the module was already loaded we don't have its code object seen_functions = set() From 01d9b50f8819fed2345e9bab93d9828fa8966ebe Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Tue, 17 Dec 2024 18:04:30 +0100 Subject: [PATCH 324/372] feat(asm): standalone sca billing (#11655) --- ddtrace/_trace/tracer.py | 6 +- ...andalone-sca-billing-925c84d69fe061ce.yaml | 4 + tests/appsec/appsec/test_asm_standalone.py | 134 +++++- tests/tracer/test_propagation.py | 441 ++++++++++-------- tests/tracer/test_tracer.py | 48 +- 5 files changed, 390 insertions(+), 243 deletions(-) create mode 100644 releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 8c82efbdf37..6027976d6dc 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -236,7 +236,9 @@ def __init__( self._iast_enabled = asm_config._iast_enabled self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled self._dogstatsd_url = agent.get_stats_url() if dogstatsd_url is None else dogstatsd_url - self._apm_opt_out = (self._asm_enabled or self._iast_enabled) and self._appsec_standalone_enabled + self._apm_opt_out = self._appsec_standalone_enabled and ( + self._asm_enabled or self._iast_enabled or config._sca_enabled + ) if self._apm_opt_out: self.enabled = False # Disable compute stats (neither agent or tracer should compute them) @@ -498,7 +500,7 @@ def configure( if appsec_standalone_enabled is not None: self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled = appsec_standalone_enabled - if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled): + if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled or config._sca_enabled): self._apm_opt_out = True self.enabled = False # Disable compute stats (neither agent or tracer should compute them) diff --git a/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml b/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml new file mode 100644 index 00000000000..733aaea6262 --- /dev/null +++ b/releasenotes/notes/feat-standalone-sca-billing-925c84d69fe061ce.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + ASM: This introduces "Standalone SCA billing", opting out for APM billing and applying to only SCA. Enable this by setting these two environment variables: ``DD_APPSEC_SCA_ENABLED`` and ``DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED`` diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py index 31624724069..6841314cea8 100644 --- a/tests/appsec/appsec/test_asm_standalone.py +++ b/tests/appsec/appsec/test_asm_standalone.py @@ -1,41 +1,145 @@ #!/usr/bin/env python3 +import copy + import pytest +import ddtrace from ddtrace.contrib.trace_utils import set_http_meta from ddtrace.ext import SpanTypes +from tests.utils import override_env @pytest.fixture( params=[ - {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, - {"iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": False}, - {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": False}, - {"iast_enabled": True, "appsec_enabled": False, "appsec_standalone_enabled": True}, - {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": True}, - {"iast_enabled": False, "appsec_enabled": True, "appsec_standalone_enabled": False}, - {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": False}, - {"iast_enabled": False, "appsec_enabled": False, "appsec_standalone_enabled": True}, - {"appsec_enabled": True}, - {"appsec_enabled": False}, - {"iast_enabled": True}, - {"iast_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "1", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + {"DD_APPSEC_SCA_ENABLED": "1", "appsec_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "1", "appsec_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "1", "iast_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": True, "appsec_enabled": True, "appsec_standalone_enabled": True}, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": True, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": True, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": True, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": False, + }, + { + "DD_APPSEC_SCA_ENABLED": "0", + "iast_enabled": False, + "appsec_enabled": False, + "appsec_standalone_enabled": True, + }, + {"DD_APPSEC_SCA_ENABLED": "0", "appsec_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "0", "appsec_enabled": False}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": True}, + {"DD_APPSEC_SCA_ENABLED": "0", "iast_enabled": False}, ] ) def tracer_appsec_standalone(request, tracer): - tracer.configure(api_version="v0.4", **request.param) - yield tracer, request.param + new_env = {k: v for k, v in request.param.items() if k.startswith("DD_")} + with override_env(new_env): + # Reset the config so it picks up the env var value + ddtrace.config._reset() + + # Copy the params to a new dict, including the env var + request_param_copy = copy.deepcopy(request.param) + + # Remove the environment variables as they are unexpected args for the tracer configure + request.param.pop("DD_APPSEC_SCA_ENABLED", None) + tracer.configure(api_version="v0.4", **request.param) + + yield tracer, request_param_copy + # Reset tracer configuration + ddtrace.config._reset() tracer.configure(api_version="v0.4", appsec_enabled=False, appsec_standalone_enabled=False, iast_enabled=False) def test_appsec_standalone_apm_enabled_metric(tracer_appsec_standalone): tracer, args = tracer_appsec_standalone + with tracer.trace("test", span_type=SpanTypes.WEB) as span: set_http_meta(span, {}, raw_uri="http://example.com/.git", status_code="404") if args.get("appsec_standalone_enabled", None) and ( - args.get("appsec_enabled", None) or args.get("iast_enabled", None) + args.get("appsec_enabled", None) + or args.get("iast_enabled", None) + or args.get("DD_APPSEC_SCA_ENABLED", "0") == "1" ): + assert tracer._apm_opt_out is True assert span.get_metric("_dd.apm.enabled") == 0.0 else: + assert tracer._apm_opt_out is False assert span.get_metric("_dd.apm.enabled") is None diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 2e1a299c4d4..61fec650a70 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -7,6 +7,7 @@ import mock import pytest +import ddtrace from ddtrace import tracer as ddtracer from ddtrace._trace._span_link import SpanLink from ddtrace._trace.context import Context @@ -45,6 +46,7 @@ from tests.contrib.fastapi.conftest import test_spans as fastapi_test_spans # noqa:F401 from tests.contrib.fastapi.conftest import tracer # noqa:F401 +from ..utils import override_env from ..utils import override_global_config @@ -318,95 +320,107 @@ def test_extract(tracer): # noqa: F811 assert len(context.get_all_baggage_items()) == 3 +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": str(USER_KEEP), - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.test=value,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": str(USER_KEEP), + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.test=value,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span0") as span: - # First span should be kept, as we keep 1 per min - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Priority is unset - assert span.context.sampling_priority is None - assert "_sampling_priority_v1" not in span._metrics - assert span.context.dd_origin == "synthetics" - assert "_dd.p.test" in span.context._meta - assert "_dd.p.appsec" not in span.context._meta + with tracer.trace("local_root_span0") as span: + # First span should be kept, as we keep 1 per min + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Priority is unset + assert span.context.sampling_priority is None + assert "_sampling_priority_v1" not in span._metrics + assert span.context.dd_origin == "synthetics" + assert "_dd.p.test" in span.context._meta + assert "_dd.p.appsec" not in span.context._meta - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - # Ensure propagation of headers is interrupted - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers + # Ensure propagation of headers is interrupted + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers - # Span priority was unset, but as we keep 1 per min, it should be kept - # Since we have a rate limiter, priorities used are USER_KEEP and USER_REJECT - assert span._metrics["_sampling_priority_v1"] == USER_KEEP + # Span priority was unset, but as we keep 1 per min, it should be kept + # Since we have a rate limiter, priorities used are USER_KEEP and USER_REJECT + assert span._metrics["_sampling_priority_v1"] == USER_KEEP - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "0"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() - headers = {} + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass - context = HTTPPropagator.extract(headers) + headers = {} - tracer.context_provider.activate(context) + context = HTTPPropagator.extract(headers) - with tracer.trace("local_root_span") as span: - assert "_dd.p.appsec" not in span.context._meta + tracer.context_provider.activate(context) - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) + with tracer.trace("local_root_span") as span: + assert "_dd.p.appsec" not in span.context._meta - # Ensure propagation of headers takes place as expected - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - # Ensure span is dropped (no appsec event upstream or in this span) - assert span._metrics["_sampling_priority_v1"] == USER_REJECT - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + # Ensure propagation of headers takes place as expected + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers + + # Ensure span is dropped (no appsec event upstream or in this span) + assert span._metrics["_sampling_priority_v1"] == USER_REJECT + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "0"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept(tracer): # noqa: F811 @@ -443,58 +457,63 @@ def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_appsec_tag_no_appsec_event_propagation_resets( - tracer, appsec_enabled, iast_enabled # noqa: F811 + tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": str(USER_KEEP), + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.test=value,any=tag", + "ot-baggage-key1": "value1", + } - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass + context = HTTPPropagator.extract(headers) - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": str(USER_KEEP), - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.test=value,any=tag", - "ot-baggage-key1": "value1", - } + tracer.context_provider.activate(context) - context = HTTPPropagator.extract(headers) + with tracer.trace("local_root_span") as span: + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Priority is unset + assert span.context.sampling_priority is None + assert "_sampling_priority_v1" not in span._metrics + assert span.context.dd_origin == "synthetics" + assert "_dd.p.test" in span.context._meta + assert "_dd.p.appsec" not in span.context._meta - tracer.context_provider.activate(context) + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) - with tracer.trace("local_root_span") as span: - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Priority is unset - assert span.context.sampling_priority is None - assert "_sampling_priority_v1" not in span._metrics - assert span.context.dd_origin == "synthetics" - assert "_dd.p.test" in span.context._meta - assert "_dd.p.appsec" not in span.context._meta + # Ensure propagation of headers takes place as expected + assert "x-datadog-origin" not in next_headers + assert "x-datadog-tags" not in next_headers + assert "x-datadog-trace-id" not in next_headers + assert "x-datadog-parent-id" not in next_headers + assert "x-datadog-sampling-priority" not in next_headers - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - - # Ensure propagation of headers takes place as expected - assert "x-datadog-origin" not in next_headers - assert "x-datadog-tags" not in next_headers - assert "x-datadog-trace-id" not in next_headers - assert "x-datadog-parent-id" not in next_headers - assert "x-datadog-sampling-priority" not in next_headers - - # Priority was unset, and trace is not kept, so it should be dropped - # As we have a rate limiter, priorities used are USER_KEEP and USER_REJECT - assert span._metrics["_sampling_priority_v1"] == USER_REJECT - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + # Priority was unset, and trace is not kept, so it should be dropped + # As we have a rate limiter, priorities used are USER_KEEP and USER_REJECT + assert span._metrics["_sampling_priority_v1"] == USER_REJECT + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": "false"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @@ -546,131 +565,141 @@ def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_user_keep( - tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 + tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass - - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": upstream_priority, - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.appsec=1,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": upstream_priority, + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.appsec=1,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span") as span: - assert span.trace_id == 1234 - assert span.parent_id == 5678 - # Enforced user keep regardless of upstream priority - assert span.context.sampling_priority == USER_KEEP - assert span.context.dd_origin == "synthetics" - assert span.context._meta == { - "_dd.origin": "synthetics", - "_dd.p.dm": "-3", - "_dd.p.appsec": "1", - } - with tracer.trace("child_span") as child_span: - assert child_span.trace_id == 1234 - assert child_span.parent_id != 5678 - assert child_span.context.sampling_priority == USER_KEEP - assert child_span.context.dd_origin == "synthetics" - assert child_span.context._meta == { + with tracer.trace("local_root_span") as span: + assert span.trace_id == 1234 + assert span.parent_id == 5678 + # Enforced user keep regardless of upstream priority + assert span.context.sampling_priority == USER_KEEP + assert span.context.dd_origin == "synthetics" + assert span.context._meta == { "_dd.origin": "synthetics", "_dd.p.dm": "-3", "_dd.p.appsec": "1", } - - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - assert next_headers["x-datadog-origin"] == "synthetics" - assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) - assert next_headers["x-datadog-trace-id"] == "1234" - assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") - - # Ensure span sets user keep regardless of received priority (appsec event upstream) - assert span._metrics["_sampling_priority_v1"] == USER_KEEP - - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + with tracer.trace("child_span") as child_span: + assert child_span.trace_id == 1234 + assert child_span.parent_id != 5678 + assert child_span.context.sampling_priority == USER_KEEP + assert child_span.context.dd_origin == "synthetics" + assert child_span.context._meta == { + "_dd.origin": "synthetics", + "_dd.p.dm": "-3", + "_dd.p.appsec": "1", + } + + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) + assert next_headers["x-datadog-origin"] == "synthetics" + assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) + assert next_headers["x-datadog-trace-id"] == "1234" + assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") + + # Ensure span sets user keep regardless of received priority (appsec event upstream) + assert span._metrics["_sampling_priority_v1"] == USER_KEEP + + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) @pytest.mark.parametrize("upstream_priority", ["1", "2"]) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_appsec_event_present_propagation_force_keep( - tracer, upstream_priority, appsec_enabled, iast_enabled # noqa: F811 + tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 ): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") - - tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) - try: - with tracer.trace("local_root_span0"): - # First span should be kept, as we keep 1 per min - pass - - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "x-datadog-sampling-priority": upstream_priority, - "x-datadog-origin": "synthetics", - "x-datadog-tags": "_dd.p.appsec=1,any=tag", - "ot-baggage-key1": "value1", - } + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=appsec_enabled, appsec_standalone_enabled=True, iast_enabled=iast_enabled) + try: + with tracer.trace("local_root_span0"): + # First span should be kept, as we keep 1 per min + pass + + headers = { + "x-datadog-trace-id": "1234", + "x-datadog-parent-id": "5678", + "x-datadog-sampling-priority": upstream_priority, + "x-datadog-origin": "synthetics", + "x-datadog-tags": "_dd.p.appsec=1,any=tag", + "ot-baggage-key1": "value1", + } - context = HTTPPropagator.extract(headers) + context = HTTPPropagator.extract(headers) - tracer.context_provider.activate(context) + tracer.context_provider.activate(context) - with tracer.trace("local_root_span") as span: - _asm_manual_keep(span) - assert span.trace_id == 1234 - assert span.parent_id == 5678 - assert span.context.sampling_priority == USER_KEEP # user keep always - assert span.context.dd_origin == "synthetics" - assert span.context._meta == { - "_dd.origin": "synthetics", - "_dd.p.dm": "-4", - "_dd.p.appsec": "1", - } - with tracer.trace("child_span") as child_span: - assert child_span.trace_id == 1234 - assert child_span.parent_id != 5678 - assert child_span.context.sampling_priority == USER_KEEP # user keep always - assert child_span.context.dd_origin == "synthetics" - assert child_span.context._meta == { + with tracer.trace("local_root_span") as span: + _asm_manual_keep(span) + assert span.trace_id == 1234 + assert span.parent_id == 5678 + assert span.context.sampling_priority == USER_KEEP # user keep always + assert span.context.dd_origin == "synthetics" + assert span.context._meta == { "_dd.origin": "synthetics", "_dd.p.dm": "-4", "_dd.p.appsec": "1", } - - next_headers = {} - HTTPPropagator.inject(span.context, next_headers) - assert next_headers["x-datadog-origin"] == "synthetics" - assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) # user keep always - assert next_headers["x-datadog-trace-id"] == "1234" - assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") - - # Ensure span set to user keep regardless received priority (appsec event upstream) - assert span._metrics["_sampling_priority_v1"] == USER_KEEP # user keep always - - finally: - tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) + with tracer.trace("child_span") as child_span: + assert child_span.trace_id == 1234 + assert child_span.parent_id != 5678 + assert child_span.context.sampling_priority == USER_KEEP # user keep always + assert child_span.context.dd_origin == "synthetics" + assert child_span.context._meta == { + "_dd.origin": "synthetics", + "_dd.p.dm": "-4", + "_dd.p.appsec": "1", + } + + next_headers = {} + HTTPPropagator.inject(span.context, next_headers) + assert next_headers["x-datadog-origin"] == "synthetics" + assert next_headers["x-datadog-sampling-priority"] == str(USER_KEEP) # user keep always + assert next_headers["x-datadog-trace-id"] == "1234" + assert next_headers["x-datadog-tags"].startswith("_dd.p.appsec=1,") + + # Ensure span set to user keep regardless received priority (appsec event upstream) + assert span._metrics["_sampling_priority_v1"] == USER_KEEP # user keep always + + finally: + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, appsec_standalone_enabled=False) def test_extract_with_baggage_http_propagation(tracer): # noqa: F811 diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index f432403d3f9..4cdcf876aba 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -2043,30 +2043,38 @@ def test_import_ddtrace_tracer_not_module(): assert isinstance(tracer, Tracer) +@pytest.mark.parametrize("sca_enabled", ["true", "false"]) @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) -def test_asm_standalone_configuration(appsec_enabled, iast_enabled): - if not appsec_enabled and not iast_enabled: - pytest.skip("AppSec or IAST must be enabled") +def test_asm_standalone_configuration(sca_enabled, appsec_enabled, iast_enabled): + if not appsec_enabled and not iast_enabled and sca_enabled == "false": + pytest.skip("SCA, AppSec or IAST must be enabled") + + with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): + ddtrace.config._reset() + tracer = ddtrace.Tracer() + tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) + if appsec_enabled: + assert tracer._asm_enabled is True + if iast_enabled: + assert tracer._iast_enabled is True + if sca_enabled == "true": + assert bool(ddtrace.config._sca_enabled) is True + + assert tracer._appsec_standalone_enabled is True + assert tracer._apm_opt_out is True + assert tracer.enabled is False + + assert isinstance(tracer._sampler.limiter, RateLimiter) + assert tracer._sampler.limiter.rate_limit == 1 + assert tracer._sampler.limiter.time_window == 60e9 + + assert tracer._compute_stats is False - tracer = ddtrace.Tracer() - tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) - if appsec_enabled: - assert tracer._asm_enabled is True - if iast_enabled: - assert tracer._iast_enabled is True - - assert tracer._appsec_standalone_enabled is True - assert tracer._apm_opt_out is True - assert tracer.enabled is False - - assert isinstance(tracer._sampler.limiter, RateLimiter) - assert tracer._sampler.limiter.rate_limit == 1 - assert tracer._sampler.limiter.time_window == 60e9 - - assert tracer._compute_stats is False # reset tracer values - tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False) + with override_env({"DD_APPSEC_SCA_ENABLED": "false"}): + ddtrace.config._reset() + tracer.configure(appsec_enabled=False, iast_enabled=False, appsec_standalone_enabled=False) def test_gc_not_used_on_root_spans(): From de9bc48a9172278d4607aac4e3f3a8778c81f109 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Tue, 17 Dec 2024 19:20:37 -0500 Subject: [PATCH 325/372] chore(ci): switch ubuntu runner image in generate package versions workflow (#11749) `ubuntu-latest` was upgraded to use `ubuntu-24.04`. This is incompatible with `python 3.7`, which we still test and support (and is needed for the `Generate Package Versions` workflow in order to build all the riot environments). This PR switches to using the `ubuntu-22.04` image (the previous latest). When we drop 3.7 support, we can switch back to `ubuntu-latest`. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/generate-package-versions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index 4db524c3d04..740edc20725 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -8,7 +8,7 @@ on: jobs: generate-package-versions: name: Generate package versions - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: actions: read contents: write From beb87f64f7e1713b8ea76ba7050e2242093cb7d0 Mon Sep 17 00:00:00 2001 From: Duncan Harvey <35278470+duncanpharvey@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:49:59 -0500 Subject: [PATCH 326/372] feat(azure_functions): add azure functions integration (#11474) This PR adds an integration for tracing the [azure-functions](https://pypi.org/project/azure-functions/) package. ### Additional Notes: - This change only supports the [v2 programming model](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=get-started%2Casgi%2Capplication-level&pivots=python-mode-decorators). If there are enough requests for the v1 programming model we can add tracing in a future PR - This change only supports tracing [Http triggers](https://github.com/Azure/azure-functions-python-library/blob/dd4fac4db0ff4ca3cd01d314a0ddf280aa59813e/azure/functions/decorators/function_app.py#L462). Tracing for other triggers will be added in future PRs - Azure Functions package currently supports Python versions `3.7` to `3.11` (no `3.12` support at the moment) - Builds off the integration work started by @gord02 in https://github.com/DataDog/dd-trace-py/pull/9726 - Dockerfile changes to testrunner made in https://github.com/DataDog/dd-trace-py/pull/11617 and https://github.com/DataDog/dd-trace-py/pull/11609 * `mariadb` install was broken in the testrunner image * [azure-functions-core-tools](https://github.com/Azure/azure-functions-core-tools) package must be installed on the test runner for tests to work - Version pinned to [4.0.6280](https://github.com/Azure/azure-functions-core-tools/releases/tag/4.0.6280) due to some issues with the most recent versions - Package only supported on `linux/amd64` architecture ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/1337ee3.txt | 26 ++++++ .riot/requirements/14b54db.txt | 24 ++++++ .riot/requirements/1e62aea.txt | 26 ++++++ .riot/requirements/73109d5.txt | 29 +++++++ .riot/requirements/c2420c2.txt | 26 ++++++ ddtrace/_monkey.py | 2 + ddtrace/_trace/trace_handlers.py | 38 +++++++++ ddtrace/contrib/azure_functions/__init__.py | 46 ++++++++++ ddtrace/contrib/azure_functions/patch.py | 14 +++ .../contrib/internal/azure_functions/patch.py | 85 +++++++++++++++++++ ddtrace/ext/__init__.py | 1 + ...unctions-integration-108911bfe1e5f081.yaml | 3 + riotfile.py | 9 ++ tests/contrib/azure_functions/__init__.py | 0 .../azure_function_app/function_app.py | 24 ++++++ .../azure_function_app/host.json | 15 ++++ .../azure_function_app/local.settings.json | 10 +++ .../test_azure_functions_patch.py | 31 +++++++ .../test_azure_functions_snapshot.py | 64 ++++++++++++++ tests/contrib/suitespec.yml | 14 +++ ...unctions_snapshot.test_http_get_error.json | 36 ++++++++ ...e_functions_snapshot.test_http_get_ok.json | 33 +++++++ ..._functions_snapshot.test_http_post_ok.json | 33 +++++++ 23 files changed, 589 insertions(+) create mode 100644 .riot/requirements/1337ee3.txt create mode 100644 .riot/requirements/14b54db.txt create mode 100644 .riot/requirements/1e62aea.txt create mode 100644 .riot/requirements/73109d5.txt create mode 100644 .riot/requirements/c2420c2.txt create mode 100644 ddtrace/contrib/azure_functions/__init__.py create mode 100644 ddtrace/contrib/azure_functions/patch.py create mode 100644 ddtrace/contrib/internal/azure_functions/patch.py create mode 100644 releasenotes/notes/feat-add-azure-functions-integration-108911bfe1e5f081.yaml create mode 100644 tests/contrib/azure_functions/__init__.py create mode 100644 tests/contrib/azure_functions/azure_function_app/function_app.py create mode 100644 tests/contrib/azure_functions/azure_function_app/host.json create mode 100644 tests/contrib/azure_functions/azure_function_app/local.settings.json create mode 100644 tests/contrib/azure_functions/test_azure_functions_patch.py create mode 100644 tests/contrib/azure_functions/test_azure_functions_snapshot.py create mode 100644 tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_error.json create mode 100644 tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_ok.json create mode 100644 tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_post_ok.json diff --git a/.riot/requirements/1337ee3.txt b/.riot/requirements/1337ee3.txt new file mode 100644 index 00000000000..1b296ead110 --- /dev/null +++ b/.riot/requirements/1337ee3.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1337ee3.in +# +attrs==24.2.0 +azure-functions==1.21.3 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.1.0 +urllib3==2.2.3 diff --git a/.riot/requirements/14b54db.txt b/.riot/requirements/14b54db.txt new file mode 100644 index 00000000000..6b103b5f841 --- /dev/null +++ b/.riot/requirements/14b54db.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/14b54db.in +# +attrs==24.2.0 +azure-functions==1.21.3 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/1e62aea.txt b/.riot/requirements/1e62aea.txt new file mode 100644 index 00000000000..4a152a7b448 --- /dev/null +++ b/.riot/requirements/1e62aea.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e62aea.in +# +attrs==24.2.0 +azure-functions==1.21.3 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.1.0 +urllib3==2.2.3 diff --git a/.riot/requirements/73109d5.txt b/.riot/requirements/73109d5.txt new file mode 100644 index 00000000000..42b5dd0e30c --- /dev/null +++ b/.riot/requirements/73109d5.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/73109d5.in +# +attrs==24.2.0 +azure-functions==1.21.3 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.2.7 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==6.7.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.0 +pluggy==1.2.0 +pytest==7.4.4 +pytest-cov==4.1.0 +pytest-mock==3.11.1 +requests==2.31.0 +sortedcontainers==2.4.0 +tomli==2.0.1 +typing-extensions==4.7.1 +urllib3==2.0.7 +zipp==3.15.0 diff --git a/.riot/requirements/c2420c2.txt b/.riot/requirements/c2420c2.txt new file mode 100644 index 00000000000..2d6d61d7a79 --- /dev/null +++ b/.riot/requirements/c2420c2.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c2420c2.in +# +attrs==24.2.0 +azure-functions==1.21.3 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.8 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.1.0 +urllib3==2.2.3 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index b0c17213130..8dd83558c83 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -94,6 +94,7 @@ "yaaredis": True, "asyncpg": True, "aws_lambda": True, # patch only in AWS Lambda environments + "azure_functions": True, "tornado": False, "openai": True, "langchain": True, @@ -143,6 +144,7 @@ "futures": ("concurrent.futures.thread",), "vertica": ("vertica_python",), "aws_lambda": ("datadog_lambda",), + "azure_functions": ("azure.functions",), "httplib": ("http.client",), "kafka": ("confluent_kafka",), "google_generativeai": ("google.generativeai",), diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py index 1807ae220f6..7c2ba02d6b4 100644 --- a/ddtrace/_trace/trace_handlers.py +++ b/ddtrace/_trace/trace_handlers.py @@ -28,6 +28,7 @@ from ddtrace.ext import http from ddtrace.internal import core from ddtrace.internal.compat import maybe_stringify +from ddtrace.internal.compat import parse from ddtrace.internal.constants import COMPONENT from ddtrace.internal.constants import FLASK_ENDPOINT from ddtrace.internal.constants import FLASK_URL_RULE @@ -675,6 +676,40 @@ def _set_span_pointer(span: "Span", span_pointer_description: _SpanPointerDescri ) +def _set_azure_function_tags(span, azure_functions_config, function_name, trigger): + span.set_tag_str(COMPONENT, azure_functions_config.integration_name) + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + span.set_tag_str("aas.function.name", function_name) # codespell:ignore + span.set_tag_str("aas.function.trigger", trigger) # codespell:ignore + + +def _on_azure_functions_request_span_modifier(ctx, azure_functions_config, req): + span = ctx.get_item("req_span") + parsed_url = parse.urlparse(req.url) + path = parsed_url.path + span.resource = f"{req.method} {path}" + trace_utils.set_http_meta( + span, + azure_functions_config, + method=req.method, + url=req.url, + request_headers=req.headers, + request_body=req.get_body(), + route=path, + ) + + +def _on_azure_functions_start_response(ctx, azure_functions_config, res, function_name, trigger): + span = ctx.get_item("req_span") + _set_azure_function_tags(span, azure_functions_config, function_name, trigger) + trace_utils.set_http_meta( + span, + azure_functions_config, + status_code=res.status_code if res else None, + response_headers=res.headers if res else None, + ) + + def listen(): core.on("wsgi.request.prepare", _on_request_prepare) core.on("wsgi.request.prepared", _on_request_prepared) @@ -723,6 +758,8 @@ def listen(): core.on("botocore.kinesis.GetRecords.post", _on_botocore_kinesis_getrecords_post) core.on("redis.async_command.post", _on_redis_command_post) core.on("redis.command.post", _on_redis_command_post) + core.on("azure.functions.request_call_modifier", _on_azure_functions_request_span_modifier) + core.on("azure.functions.start_response", _on_azure_functions_start_response) core.on("test_visibility.enable", _on_test_visibility_enable) core.on("test_visibility.disable", _on_test_visibility_disable) @@ -754,6 +791,7 @@ def listen(): "rq.worker.perform_job", "rq.job.perform", "rq.job.fetch_many", + "azure.functions.patched_route_request", ): core.on(f"context.started.start_span.{context_name}", _start_span) diff --git a/ddtrace/contrib/azure_functions/__init__.py b/ddtrace/contrib/azure_functions/__init__.py new file mode 100644 index 00000000000..208b971efaa --- /dev/null +++ b/ddtrace/contrib/azure_functions/__init__.py @@ -0,0 +1,46 @@ +""" +The azure_functions integration traces all http requests to your Azure Function app. + +Enabling +~~~~~~~~ + +Use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(azure_functions=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.azure_functions["service"] + + The service name reported by default for azure_functions instances. + + This option can also be set with the ``DD_SERVICE`` environment + variable. + + Default: ``"azure_functions"`` + +""" + +from ddtrace.internal.utils.importlib import require_modules + + +required_modules = ["azure.functions"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + # Required to allow users to import from `ddtrace.contrib.azure_functions.patch` directly + import warnings as _w + + with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 + + # Expose public methods + from ddtrace.contrib.internal.azure_functions.patch import get_version + from ddtrace.contrib.internal.azure_functions.patch import patch + from ddtrace.contrib.internal.azure_functions.patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/azure_functions/patch.py b/ddtrace/contrib/azure_functions/patch.py new file mode 100644 index 00000000000..1a23613972d --- /dev/null +++ b/ddtrace/contrib/azure_functions/patch.py @@ -0,0 +1,14 @@ +from ddtrace.contrib.internal.azure_functions.patch import * # noqa: F403 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + category=DDTraceDeprecationWarning, + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) diff --git a/ddtrace/contrib/internal/azure_functions/patch.py b/ddtrace/contrib/internal/azure_functions/patch.py new file mode 100644 index 00000000000..15089a2e733 --- /dev/null +++ b/ddtrace/contrib/internal/azure_functions/patch.py @@ -0,0 +1,85 @@ +import azure.functions as azure_functions +from wrapt import wrap_function_wrapper as _w + +from ddtrace import config +from ddtrace.contrib.trace_utils import int_service +from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.ext import SpanTypes +from ddtrace.internal import core +from ddtrace.internal.schema import schematize_cloud_faas_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.pin import Pin + + +config._add( + "azure_functions", + { + "_default_service": schematize_service_name("azure_functions"), + }, +) + + +def get_version(): + # type: () -> str + return getattr(azure_functions, "__version__", "") + + +def patch(): + """ + Patch `azure.functions` module for tracing + """ + # Check to see if we have patched azure.functions yet or not + if getattr(azure_functions, "_datadog_patch", False): + return + azure_functions._datadog_patch = True + + Pin().onto(azure_functions.FunctionApp) + _w("azure.functions", "FunctionApp.route", _patched_route) + + +def _patched_route(wrapped, instance, args, kwargs): + trigger = "Http" + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + def _wrapper(func): + function_name = func.__name__ + + def wrap_function(req: azure_functions.HttpRequest, context: azure_functions.Context): + operation_name = schematize_cloud_faas_operation( + "azure.functions.invoke", cloud_provider="azure", cloud_service="functions" + ) + with core.context_with_data( + "azure.functions.patched_route_request", + span_name=operation_name, + pin=pin, + service=int_service(pin, config.azure_functions), + span_type=SpanTypes.SERVERLESS, + ) as ctx, ctx.span: + ctx.set_item("req_span", ctx.span) + core.dispatch("azure.functions.request_call_modifier", (ctx, config.azure_functions, req)) + res = None + try: + res = func(req) + return res + finally: + core.dispatch( + "azure.functions.start_response", (ctx, config.azure_functions, res, function_name, trigger) + ) + + # Needed to correctly display function name when running 'func start' locally + wrap_function.__name__ = function_name + + return wrapped(*args, **kwargs)(wrap_function) + + return _wrapper + + +def unpatch(): + if not getattr(azure_functions, "_datadog_patch", False): + return + azure_functions._datadog_patch = False + + _u(azure_functions.FunctionApp, "route") diff --git a/ddtrace/ext/__init__.py b/ddtrace/ext/__init__.py index 2387dbd63a4..965dd04f43f 100644 --- a/ddtrace/ext/__init__.py +++ b/ddtrace/ext/__init__.py @@ -7,6 +7,7 @@ class SpanTypes(object): HTTP = "http" MONGODB = "mongodb" REDIS = "redis" + SERVERLESS = "serverless" SQL = "sql" TEMPLATE = "template" TEST = "test" diff --git a/releasenotes/notes/feat-add-azure-functions-integration-108911bfe1e5f081.yaml b/releasenotes/notes/feat-add-azure-functions-integration-108911bfe1e5f081.yaml new file mode 100644 index 00000000000..b9b7b255564 --- /dev/null +++ b/releasenotes/notes/feat-add-azure-functions-integration-108911bfe1e5f081.yaml @@ -0,0 +1,3 @@ +features: + - | + azure_functions: This introduces support for Azure Functions. diff --git a/riotfile.py b/riotfile.py index 567a5f65d66..86d6ed5bf76 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2837,6 +2837,15 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "envier": "==0.5.2", }, ), + Venv( + name="azure_functions", + command="pytest {cmdargs} tests/contrib/azure_functions", + pys=select_pys(min_version="3.7", max_version="3.11"), + pkgs={ + "azure.functions": latest, + "requests": latest, + }, + ), Venv( name="sourcecode", command="pytest {cmdargs} tests/sourcecode", diff --git a/tests/contrib/azure_functions/__init__.py b/tests/contrib/azure_functions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/contrib/azure_functions/azure_function_app/function_app.py b/tests/contrib/azure_functions/azure_function_app/function_app.py new file mode 100644 index 00000000000..edff02b0bb0 --- /dev/null +++ b/tests/contrib/azure_functions/azure_function_app/function_app.py @@ -0,0 +1,24 @@ +from ddtrace import patch + + +patch(azure_functions=True) + +import azure.functions as func # noqa: E402 + + +app = func.FunctionApp() + + +@app.route(route="httpgetok", auth_level=func.AuthLevel.ANONYMOUS, methods=[func.HttpMethod.GET]) +def http_get_ok(req: func.HttpRequest) -> func.HttpResponse: + return func.HttpResponse("Hello Datadog!") + + +@app.route(route="httpgeterror", auth_level=func.AuthLevel.ANONYMOUS, methods=[func.HttpMethod.GET]) +def http_get_error(req: func.HttpRequest) -> func.HttpResponse: + raise Exception("Test Error") + + +@app.route(route="httppostok", auth_level=func.AuthLevel.ANONYMOUS, methods=[func.HttpMethod.POST]) +def http_post_ok(req: func.HttpRequest) -> func.HttpResponse: + return func.HttpResponse("Hello Datadog!") diff --git a/tests/contrib/azure_functions/azure_function_app/host.json b/tests/contrib/azure_functions/azure_function_app/host.json new file mode 100644 index 00000000000..06d01bdaa95 --- /dev/null +++ b/tests/contrib/azure_functions/azure_function_app/host.json @@ -0,0 +1,15 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} diff --git a/tests/contrib/azure_functions/azure_function_app/local.settings.json b/tests/contrib/azure_functions/azure_function_app/local.settings.json new file mode 100644 index 00000000000..fb38bf93ca8 --- /dev/null +++ b/tests/contrib/azure_functions/azure_function_app/local.settings.json @@ -0,0 +1,10 @@ +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "python", + "FUNCTIONS_EXTENSION_VERSION": "~4", + "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", + "AzureWebJobsStorage": "", + "WEBSITE_SITE_NAME": "test-func" + } +} diff --git a/tests/contrib/azure_functions/test_azure_functions_patch.py b/tests/contrib/azure_functions/test_azure_functions_patch.py new file mode 100644 index 00000000000..acc58df654a --- /dev/null +++ b/tests/contrib/azure_functions/test_azure_functions_patch.py @@ -0,0 +1,31 @@ +# This test script was automatically generated by the contrib-patch-tests.py +# script. If you want to make changes to it, you should make sure that you have +# removed the ``_generated`` suffix from the file name, to prevent the content +# from being overwritten by future re-generations. + +from ddtrace.contrib.azure_functions import get_version +from ddtrace.contrib.azure_functions.patch import patch + + +try: + from ddtrace.contrib.azure_functions.patch import unpatch +except ImportError: + unpatch = None +from tests.contrib.patch import PatchTestCase + + +class TestAzure_FunctionsPatch(PatchTestCase.Base): + __integration_name__ = "azure_functions" + __module_name__ = "azure.functions" + __patch_func__ = patch + __unpatch_func__ = unpatch + __get_version__ = get_version + + def assert_module_patched(self, azure_functions): + pass + + def assert_not_module_patched(self, azure_functions): + pass + + def assert_not_module_double_patched(self, azure_functions): + pass diff --git a/tests/contrib/azure_functions/test_azure_functions_snapshot.py b/tests/contrib/azure_functions/test_azure_functions_snapshot.py new file mode 100644 index 00000000000..c236122181f --- /dev/null +++ b/tests/contrib/azure_functions/test_azure_functions_snapshot.py @@ -0,0 +1,64 @@ +import os +import signal +import subprocess +import time + +import pytest + +from tests.webclient import Client + + +DEFAULT_HEADERS = { + "User-Agent": "python-httpx/x.xx.x", +} + + +@pytest.fixture +def azure_functions_client(): + # Copy the env to get the correct PYTHONPATH and such + # from the virtualenv. + # webservers might exec or fork into another process, so we need to os.setsid() to create a process group + # (all of which will listen to signals sent to the parent) so that we can kill the whole application. + proc = subprocess.Popen( + ["func", "start"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + close_fds=True, + env=os.environ.copy(), + preexec_fn=os.setsid, + cwd=os.path.join(os.path.dirname(__file__), "azure_function_app"), + ) + try: + client = Client("http://0.0.0.0:7071") + # Wait for the server to start up + try: + client.wait(delay=0.5) + yield client + client.get_ignored("/shutdown") + except Exception: + pass + # At this point the traces have been sent to the test agent + # but the test agent hasn't necessarily finished processing + # the traces (race condition) so wait just a bit for that + # processing to complete. + time.sleep(1) + finally: + os.killpg(proc.pid, signal.SIGKILL) + proc.wait() + + +@pytest.mark.snapshot +def test_http_get_ok(azure_functions_client: Client) -> None: + assert azure_functions_client.get("/api/httpgetok?key=val", headers=DEFAULT_HEADERS).status_code == 200 + + +@pytest.mark.snapshot(ignores=["meta.error.stack"]) +def test_http_get_error(azure_functions_client: Client) -> None: + assert azure_functions_client.get("/api/httpgeterror", headers=DEFAULT_HEADERS).status_code == 500 + + +@pytest.mark.snapshot +def test_http_post_ok(azure_functions_client: Client) -> None: + assert ( + azure_functions_client.post("/api/httppostok", headers=DEFAULT_HEADERS, data={"key": "val"}).status_code == 200 + ) diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 2f14127ddf0..83a48ea1f48 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -23,6 +23,9 @@ components: - ddtrace/contrib/aws_lambda/* - ddtrace/contrib/internal/aws_lambda/* - ddtrace/ext/aws.py + azure_functions: + - ddtrace/contrib/azure_functions/* + - ddtrace/contrib/internal/azure_functions/* botocore: - ddtrace/contrib/botocore/* - ddtrace/contrib/internal/botocore/* @@ -374,6 +377,17 @@ suites: - tests/snapshots/tests.{suite}.* runner: riot snapshot: true + azure_functions: + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@azure_functions' + - tests/contrib/azure_functions/* + - tests/snapshots/tests.contrib.azure_functions.* + runner: riot + snapshot: true botocore: parallelism: 6 paths: diff --git a/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_error.json b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_error.json new file mode 100644 index 00000000000..4e0cf3e81b1 --- /dev/null +++ b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_error.json @@ -0,0 +1,36 @@ +[[ + { + "name": "azure.functions.invoke", + "service": "test-func", + "resource": "GET /api/httpgeterror", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "serverless", + "error": 1, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6750ad8500000000", + "aas.function.name": "http_get_error", + "aas.function.trigger": "Http", + "component": "azure_functions", + "error.message": "Test Error", + "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/internal/azure_functions/patch.py\", line 65, in wrap_function\n res = func(req)\n ^^^^^^^^^\n File \"/root/project/tests/contrib/azure_functions/azure_function_app/function_app.py\", line 19, in http_get_error\n raise Exception(\"Test Error\")\nException: Test Error\n", + "error.type": "builtins.Exception", + "http.method": "GET", + "http.route": "/api/httpgeterror", + "http.url": "http://0.0.0.0:7071/api/httpgeterror", + "http.useragent": "python-httpx/x.xx.x", + "language": "python", + "runtime-id": "d7efb82603894b91af0e18f95bfb40ce", + "span.kind": "server" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 98042 + }, + "duration": 3862875, + "start": 1733340549814399761 + }]] diff --git a/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_ok.json b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_ok.json new file mode 100644 index 00000000000..415678e4dec --- /dev/null +++ b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_get_ok.json @@ -0,0 +1,33 @@ +[[ + { + "name": "azure.functions.invoke", + "service": "test-func", + "resource": "GET /api/httpgetok", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "serverless", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6750ad7d00000000", + "aas.function.name": "http_get_ok", + "aas.function.trigger": "Http", + "component": "azure_functions", + "http.method": "GET", + "http.route": "/api/httpgetok", + "http.status_code": "200", + "http.url": "http://0.0.0.0:7071/api/httpgetok?key=val", + "http.useragent": "python-httpx/x.xx.x", + "language": "python", + "runtime-id": "2dd77b70098048f5a6b7d3a7d53d1082", + "span.kind": "server" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 96455 + }, + "duration": 1160792, + "start": 1733340541444015424 + }]] diff --git a/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_post_ok.json b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_post_ok.json new file mode 100644 index 00000000000..44c0491b7a7 --- /dev/null +++ b/tests/snapshots/tests.contrib.azure_functions.test_azure_functions_snapshot.test_http_post_ok.json @@ -0,0 +1,33 @@ +[[ + { + "name": "azure.functions.invoke", + "service": "test-func", + "resource": "POST /api/httppostok", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "serverless", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6750ad8e00000000", + "aas.function.name": "http_post_ok", + "aas.function.trigger": "Http", + "component": "azure_functions", + "http.method": "POST", + "http.route": "/api/httppostok", + "http.status_code": "200", + "http.url": "http://0.0.0.0:7071/api/httppostok", + "http.useragent": "python-httpx/x.xx.x", + "language": "python", + "runtime-id": "891babf5be3d4b86bd44163cd50c74b0", + "span.kind": "server" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 99631 + }, + "duration": 293958, + "start": 1733340558198232376 + }]] From 59c068fcaa8841ea1d9389b90f72dffed654cb26 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Wed, 18 Dec 2024 11:52:22 +0100 Subject: [PATCH 327/372] refactor(iast): simplify ``__mod__`` aspect (#11601) --- .../_taint_tracking/Aspects/AspectModulo.cpp | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp index a08f76d9f3d..b7454de26f8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp @@ -2,7 +2,7 @@ #include "Helpers.h" static PyObject* -do_modulo(PyObject* text, PyObject* insert_tuple_or_obj) +do_modulo(PyObject* text, PyObject* insert_tuple_or_obj, py::object py_candidate_text, py::object py_candidate_tuple) { PyObject* result = nullptr; @@ -13,18 +13,22 @@ do_modulo(PyObject* text, PyObject* insert_tuple_or_obj) Py_INCREF(insert_tuple); } else { insert_tuple = PyTuple_Pack(1, insert_tuple_or_obj); - if (insert_tuple == nullptr) { - return nullptr; - } } - if (PyUnicode_Check(text)) { + if (PyUnicode_Check(text) && insert_tuple != nullptr) { result = PyUnicode_Format(text, insert_tuple); - } else if (PyBytes_Check(text) or PyByteArray_Check(text)) { - auto method_name = PyUnicode_FromString("__mod__"); - result = PyObject_CallMethodObjArgs(text, method_name, insert_tuple, nullptr); - Py_DECREF(method_name); } else { + try { + py::object res_py = py_candidate_text.attr("__mod__")(py_candidate_tuple); + PyObject* res_pyo = res_py.ptr(); + if (res_pyo != nullptr) { + Py_INCREF(res_pyo); + } + return res_pyo; + } catch (py::error_already_set& e) { + e.restore(); + return nullptr; + } } Py_DECREF(insert_tuple); if (has_pyerr()) { @@ -49,21 +53,7 @@ api_modulo_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) // Lambda to get the result of the modulo operation auto get_result = [&]() -> PyObject* { - PyObject* res = do_modulo(candidate_text, candidate_tuple); - if (res == nullptr) { - try { - py::object res_py = py_candidate_text.attr("__mod__")(py_candidate_tuple); - PyObject* res_pyo = res_py.ptr(); - if (res_pyo != nullptr) { - Py_INCREF(res_pyo); - } - return res_pyo; - } catch (py::error_already_set& e) { - e.restore(); - return nullptr; - } - } - return res; + return do_modulo(candidate_text, candidate_tuple, py_candidate_text, py_candidate_tuple); }; TRY_CATCH_ASPECT("modulo_aspect", return get_result(), , { @@ -107,7 +97,10 @@ api_modulo_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) } py::tuple formatted_parameters(list_formatted_parameters); - PyObject* applied_params = do_modulo(StringToPyObject(fmttext, py_str_type).ptr(), formatted_parameters.ptr()); + PyObject* applied_params = do_modulo(StringToPyObject(fmttext, py_str_type).ptr(), + formatted_parameters.ptr(), + StringToPyObject(fmttext, py_str_type), + formatted_parameters); if (applied_params == nullptr) { return get_result(); } From a7e94042b42aa696bd38538e3453ca3633dbac9b Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 18 Dec 2024 10:09:53 -0500 Subject: [PATCH 328/372] ci(celery): increase amqp task timeout (#11741) --- tests/contrib/celery/test_tagging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/contrib/celery/test_tagging.py b/tests/contrib/celery/test_tagging.py index af40c4f9209..2809364ba13 100644 --- a/tests/contrib/celery/test_tagging.py +++ b/tests/contrib/celery/test_tagging.py @@ -102,7 +102,7 @@ def test_amqp_task(instrument_celery, traced_amqp_celery_app): shutdown_timeout=30, ): t = add.delay(4, 4) - assert t.get(timeout=2) == 8 + assert t.get(timeout=30) == 8 # wait for spans to be received time.sleep(3) From e46e3b5785ee26aec4fbf9e7cd2c13c40a435697 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 18 Dec 2024 11:13:43 -0500 Subject: [PATCH 329/372] chore(profiling): remove unused mutex (#11774) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/include/sample_manager.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample_manager.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample_manager.hpp index baf6af2b33a..30c4048e967 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample_manager.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/sample_manager.hpp @@ -19,7 +19,6 @@ class SampleManager private: static inline unsigned int max_nframes{ g_default_max_nframes }; static inline SampleType type_mask{ SampleType::All }; - static inline std::mutex init_mutex{}; static inline size_t sample_pool_capacity{ g_default_sample_pool_capacity }; static inline std::unique_ptr sample_pool{ nullptr }; From c7b888d09cdfba1186c49a91a7370b8bdccb5648 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 18 Dec 2024 11:23:24 -0500 Subject: [PATCH 330/372] ci: test with Python 3.13 (#10821) This change adjusts CI and the library itself to work under Python 3.13. Any tests that failed under 3.13 are skipped on 3.13 for now and will be unskipped in a future change. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler Co-authored-by: Gabriele N. Tornetta Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Co-authored-by: Federico Mon Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Co-authored-by: Gabriele N. Tornetta --- .github/workflows/build_deploy.yml | 2 +- .github/workflows/build_python_3.yml | 12 +- .../workflows/generate-package-versions.yml | 5 + .github/workflows/requirements-locks.yml | 4 +- .gitlab/download-dependency-wheels.sh | 2 +- .gitlab/package.yml | 21 +++ .gitlab/testrunner.yml | 4 +- .gitlab/tests.yml | 6 +- .riot/requirements/102dfdd.txt | 20 +++ .riot/requirements/104daf8.txt | 25 +++ .riot/requirements/104f450.txt | 20 +++ .riot/requirements/1053dce.txt | 26 +++ .riot/requirements/114bad8.txt | 29 ++++ .riot/requirements/11f2bd0.txt | 38 ++++ .riot/requirements/11fd02a.txt | 19 ++ .riot/requirements/1261ed3.txt | 31 ++++ .riot/requirements/1304e20.txt | 26 +++ .riot/requirements/1332b9d.txt | 38 ++++ .riot/requirements/13658ae.txt | 24 +++ .riot/requirements/136fddd.txt | 21 +++ .riot/requirements/1374394.txt | 34 ++++ .riot/requirements/1381214.txt | 21 +++ .riot/requirements/13ae267.txt | 20 +++ .riot/requirements/141bfd1.txt | 32 ++++ .riot/requirements/141f7eb.txt | 24 +++ .riot/requirements/1463930.txt | 20 +++ .riot/requirements/14be2f6.txt | 25 +++ .riot/requirements/14d7e8a.txt | 31 ++++ .riot/requirements/14f1594.txt | 21 +++ .riot/requirements/152e97f.txt | 21 +++ .riot/requirements/1584f8c.txt | 29 ++++ .riot/requirements/164c3ce.txt | 31 ++++ .riot/requirements/167b853.txt | 21 +++ .riot/requirements/16acf84.txt | 27 +++ .riot/requirements/16cc321.txt | 20 +++ .riot/requirements/16d2d1f.txt | 48 +++++ .riot/requirements/16de9c4.txt | 37 ++++ .riot/requirements/178f7d5.txt | 20 +++ .riot/requirements/17d40ef.txt | 20 +++ .riot/requirements/1819cb6.txt | 29 ++++ .riot/requirements/188244e.txt | 20 +++ .riot/requirements/18c6e70.txt | 19 ++ .riot/requirements/18e9526.txt | 28 +++ .riot/requirements/192c7c0.txt | 22 +++ .riot/requirements/19bbf6d.txt | 22 +++ .riot/requirements/1a485c9.txt | 23 +++ .riot/requirements/1a508dc.txt | 30 ++++ .riot/requirements/1acabe0.txt | 20 +++ .riot/requirements/1ada88e.txt | 29 ++++ .riot/requirements/1aed5dc.txt | 30 ++++ .riot/requirements/1b86c06.txt | 27 +++ .riot/requirements/1b8d922.txt | 21 +++ .riot/requirements/1ba390a.txt | 21 +++ .riot/requirements/1bf4d76.txt | 23 +++ .riot/requirements/1c22cf9.txt | 20 +++ .riot/requirements/1cb554e.txt | 21 +++ .riot/requirements/1ce0711.txt | 24 +++ .riot/requirements/1ce93b3.txt | 22 +++ .riot/requirements/1d74d67.txt | 24 +++ .riot/requirements/1d8a93c.txt | 48 +++++ .riot/requirements/1dd5678.txt | 30 ++++ .../requirements/{15e6ff4.txt => 1df4764.txt} | 32 ++-- .riot/requirements/1e19c17.txt | 29 ++++ .riot/requirements/1e4bb51.txt | 24 +++ .riot/requirements/1e4dfe1.txt | 28 +++ .riot/requirements/1e659c4.txt | 20 +++ .riot/requirements/1e70094.txt | 42 +++++ .riot/requirements/1ebb239.txt | 35 ++++ .riot/requirements/1ec9462.txt | 20 +++ .riot/requirements/1f3b209.txt | 20 +++ .riot/requirements/1fa3005.txt | 21 +++ .riot/requirements/1fc9ecc.txt | 20 +++ .riot/requirements/1fe8dd2.txt | 83 +++++++++ .riot/requirements/248da41.txt | 24 +++ .riot/requirements/2538ed0.txt | 23 +++ .riot/requirements/2581b3a.txt | 20 +++ .riot/requirements/2644218.txt | 22 +++ .riot/requirements/27d0ff8.txt | 21 +++ .riot/requirements/27e3d7b.txt | 21 +++ .riot/requirements/2d6c3d0.txt | 20 +++ .riot/requirements/2dd0811.txt | 21 +++ .riot/requirements/3ab519c.txt | 28 +++ .riot/requirements/3b804dc.txt | 28 +++ .riot/requirements/3c3f295.txt | 23 +++ .riot/requirements/3dd53da.txt | 22 +++ .riot/requirements/3f1be84.txt | 23 +++ .../requirements/{1edf426.txt => 4132bce.txt} | 12 +- .riot/requirements/44eeaa9.txt | 28 +++ .riot/requirements/4fd1520.txt | 23 +++ .riot/requirements/5b922fc.txt | 45 +++++ .riot/requirements/6cf373b.txt | 19 ++ .riot/requirements/70e034f.txt | 24 +++ .riot/requirements/74ccb83.txt | 20 +++ .riot/requirements/788c304.txt | 27 +++ .riot/requirements/7a40e08.txt | 22 +++ .../requirements/{921bc6c.txt => 7bbf828.txt} | 32 ++-- .riot/requirements/8ce955f.txt | 28 +++ .riot/requirements/91fe586.txt | 25 +++ .riot/requirements/9a07d4a.txt | 23 +++ .riot/requirements/9a5c0d9.txt | 32 ++++ .riot/requirements/a0cc2a4.txt | 21 +++ .riot/requirements/a9f396a.txt | 31 ++++ .riot/requirements/ae8bd25.txt | 26 +++ .riot/requirements/b29075f.txt | 38 ++++ .riot/requirements/b403d9d.txt | 49 ++++++ .riot/requirements/bc64f49.txt | 35 ++++ .riot/requirements/bc7a1f4.txt | 21 +++ .riot/requirements/bcbec2a.txt | 46 +++++ .riot/requirements/bebdd41.txt | 19 ++ .riot/requirements/c1351c9.txt | 21 +++ .riot/requirements/c4d4455.txt | 20 +++ .riot/requirements/c77bbb6.txt | 48 +++++ .riot/requirements/c8b476b.txt | 32 ++++ .riot/requirements/d5098dd.txt | 22 +++ .riot/requirements/d7dfbc2.txt | 22 +++ .riot/requirements/d81ad99.txt | 20 +++ .riot/requirements/db78045.txt | 21 +++ .riot/requirements/dbc6a48.txt | 35 ++++ .riot/requirements/dbeb1d7.txt | 22 +++ .riot/requirements/ddd8721.txt | 20 +++ .riot/requirements/dedea98.txt | 20 +++ .riot/requirements/df7a937.txt | 20 +++ .riot/requirements/e06abee.txt | 38 ++++ .riot/requirements/e20152c.txt | 20 +++ .riot/requirements/e2bf559.txt | 23 +++ .riot/requirements/ee48b16.txt | 22 +++ .riot/requirements/f20c964.txt | 30 ++++ .riot/requirements/f339e99.txt | 19 ++ .riot/requirements/f33b994.txt | 23 +++ .riot/requirements/f46a802.txt | 20 +++ .riot/requirements/f4fafb3.txt | 48 +++++ .riot/requirements/fbee8ab.txt | 25 +++ .../appsec/_iast/_taint_tracking/__init__.py | 14 +- ddtrace/debugging/_expressions.py | 26 +-- ddtrace/internal/_threads.cpp | 46 ++++- ddtrace/internal/injection.py | 25 ++- ddtrace/internal/wrapping/__init__.py | 2 + ddtrace/internal/wrapping/context.py | 50 +++++- ddtrace/profiling/collector/stack.pyx | 6 +- docker-compose.yml | 4 + docker/.python-version | 2 +- docker/Dockerfile | 11 +- docs/versioning.rst | 6 +- hatch.toml | 8 +- lib-injection/dl_wheels.py | 5 +- lib-injection/sources/sitecustomize.py | 2 +- pyproject.toml | 5 +- .../notes/threethirteen-d40d659d8939fe5e.yaml | 4 + riotfile.py | 85 ++++----- setup.py | 4 +- src/core/Cargo.lock | 164 +++--------------- src/core/Cargo.toml | 2 +- tests/contrib/futures/test_propagation.py | 2 + .../crashtracker/test_crashtracker.py | 1 + tests/internal/symbol_db/test_symbols.py | 2 + tests/internal/test_forksafe.py | 2 + tests/internal/test_injection.py | 2 + tests/internal/test_wrapping.py | 9 + 158 files changed, 3540 insertions(+), 284 deletions(-) create mode 100644 .riot/requirements/102dfdd.txt create mode 100644 .riot/requirements/104daf8.txt create mode 100644 .riot/requirements/104f450.txt create mode 100644 .riot/requirements/1053dce.txt create mode 100644 .riot/requirements/114bad8.txt create mode 100644 .riot/requirements/11f2bd0.txt create mode 100644 .riot/requirements/11fd02a.txt create mode 100644 .riot/requirements/1261ed3.txt create mode 100644 .riot/requirements/1304e20.txt create mode 100644 .riot/requirements/1332b9d.txt create mode 100644 .riot/requirements/13658ae.txt create mode 100644 .riot/requirements/136fddd.txt create mode 100644 .riot/requirements/1374394.txt create mode 100644 .riot/requirements/1381214.txt create mode 100644 .riot/requirements/13ae267.txt create mode 100644 .riot/requirements/141bfd1.txt create mode 100644 .riot/requirements/141f7eb.txt create mode 100644 .riot/requirements/1463930.txt create mode 100644 .riot/requirements/14be2f6.txt create mode 100644 .riot/requirements/14d7e8a.txt create mode 100644 .riot/requirements/14f1594.txt create mode 100644 .riot/requirements/152e97f.txt create mode 100644 .riot/requirements/1584f8c.txt create mode 100644 .riot/requirements/164c3ce.txt create mode 100644 .riot/requirements/167b853.txt create mode 100644 .riot/requirements/16acf84.txt create mode 100644 .riot/requirements/16cc321.txt create mode 100644 .riot/requirements/16d2d1f.txt create mode 100644 .riot/requirements/16de9c4.txt create mode 100644 .riot/requirements/178f7d5.txt create mode 100644 .riot/requirements/17d40ef.txt create mode 100644 .riot/requirements/1819cb6.txt create mode 100644 .riot/requirements/188244e.txt create mode 100644 .riot/requirements/18c6e70.txt create mode 100644 .riot/requirements/18e9526.txt create mode 100644 .riot/requirements/192c7c0.txt create mode 100644 .riot/requirements/19bbf6d.txt create mode 100644 .riot/requirements/1a485c9.txt create mode 100644 .riot/requirements/1a508dc.txt create mode 100644 .riot/requirements/1acabe0.txt create mode 100644 .riot/requirements/1ada88e.txt create mode 100644 .riot/requirements/1aed5dc.txt create mode 100644 .riot/requirements/1b86c06.txt create mode 100644 .riot/requirements/1b8d922.txt create mode 100644 .riot/requirements/1ba390a.txt create mode 100644 .riot/requirements/1bf4d76.txt create mode 100644 .riot/requirements/1c22cf9.txt create mode 100644 .riot/requirements/1cb554e.txt create mode 100644 .riot/requirements/1ce0711.txt create mode 100644 .riot/requirements/1ce93b3.txt create mode 100644 .riot/requirements/1d74d67.txt create mode 100644 .riot/requirements/1d8a93c.txt create mode 100644 .riot/requirements/1dd5678.txt rename .riot/requirements/{15e6ff4.txt => 1df4764.txt} (65%) create mode 100644 .riot/requirements/1e19c17.txt create mode 100644 .riot/requirements/1e4bb51.txt create mode 100644 .riot/requirements/1e4dfe1.txt create mode 100644 .riot/requirements/1e659c4.txt create mode 100644 .riot/requirements/1e70094.txt create mode 100644 .riot/requirements/1ebb239.txt create mode 100644 .riot/requirements/1ec9462.txt create mode 100644 .riot/requirements/1f3b209.txt create mode 100644 .riot/requirements/1fa3005.txt create mode 100644 .riot/requirements/1fc9ecc.txt create mode 100644 .riot/requirements/1fe8dd2.txt create mode 100644 .riot/requirements/248da41.txt create mode 100644 .riot/requirements/2538ed0.txt create mode 100644 .riot/requirements/2581b3a.txt create mode 100644 .riot/requirements/2644218.txt create mode 100644 .riot/requirements/27d0ff8.txt create mode 100644 .riot/requirements/27e3d7b.txt create mode 100644 .riot/requirements/2d6c3d0.txt create mode 100644 .riot/requirements/2dd0811.txt create mode 100644 .riot/requirements/3ab519c.txt create mode 100644 .riot/requirements/3b804dc.txt create mode 100644 .riot/requirements/3c3f295.txt create mode 100644 .riot/requirements/3dd53da.txt create mode 100644 .riot/requirements/3f1be84.txt rename .riot/requirements/{1edf426.txt => 4132bce.txt} (70%) create mode 100644 .riot/requirements/44eeaa9.txt create mode 100644 .riot/requirements/4fd1520.txt create mode 100644 .riot/requirements/5b922fc.txt create mode 100644 .riot/requirements/6cf373b.txt create mode 100644 .riot/requirements/70e034f.txt create mode 100644 .riot/requirements/74ccb83.txt create mode 100644 .riot/requirements/788c304.txt create mode 100644 .riot/requirements/7a40e08.txt rename .riot/requirements/{921bc6c.txt => 7bbf828.txt} (65%) create mode 100644 .riot/requirements/8ce955f.txt create mode 100644 .riot/requirements/91fe586.txt create mode 100644 .riot/requirements/9a07d4a.txt create mode 100644 .riot/requirements/9a5c0d9.txt create mode 100644 .riot/requirements/a0cc2a4.txt create mode 100644 .riot/requirements/a9f396a.txt create mode 100644 .riot/requirements/ae8bd25.txt create mode 100644 .riot/requirements/b29075f.txt create mode 100644 .riot/requirements/b403d9d.txt create mode 100644 .riot/requirements/bc64f49.txt create mode 100644 .riot/requirements/bc7a1f4.txt create mode 100644 .riot/requirements/bcbec2a.txt create mode 100644 .riot/requirements/bebdd41.txt create mode 100644 .riot/requirements/c1351c9.txt create mode 100644 .riot/requirements/c4d4455.txt create mode 100644 .riot/requirements/c77bbb6.txt create mode 100644 .riot/requirements/c8b476b.txt create mode 100644 .riot/requirements/d5098dd.txt create mode 100644 .riot/requirements/d7dfbc2.txt create mode 100644 .riot/requirements/d81ad99.txt create mode 100644 .riot/requirements/db78045.txt create mode 100644 .riot/requirements/dbc6a48.txt create mode 100644 .riot/requirements/dbeb1d7.txt create mode 100644 .riot/requirements/ddd8721.txt create mode 100644 .riot/requirements/dedea98.txt create mode 100644 .riot/requirements/df7a937.txt create mode 100644 .riot/requirements/e06abee.txt create mode 100644 .riot/requirements/e20152c.txt create mode 100644 .riot/requirements/e2bf559.txt create mode 100644 .riot/requirements/ee48b16.txt create mode 100644 .riot/requirements/f20c964.txt create mode 100644 .riot/requirements/f339e99.txt create mode 100644 .riot/requirements/f33b994.txt create mode 100644 .riot/requirements/f46a802.txt create mode 100644 .riot/requirements/f4fafb3.txt create mode 100644 .riot/requirements/fbee8ab.txt create mode 100644 releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index df5184f83e5..bc6a8b0b3d2 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -25,7 +25,7 @@ jobs: build_wheels: uses: ./.github/workflows/build_python_3.yml with: - cibw_build: 'cp37* cp38* cp39* cp310* cp311* cp312*' + cibw_build: 'cp37* cp38* cp39* cp310* cp311* cp312* cp313*' build_sdist: name: Build source distribution diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index 02832a008b9..fac67e45f82 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.8' - - run: pip install cibuildwheel==2.16.5 + - run: pip install cibuildwheel==2.22.0 - id: set-matrix env: CIBW_BUILD: ${{ inputs.cibw_build }} @@ -34,7 +34,7 @@ jobs: { cibuildwheel --print-build-identifiers --platform linux --arch x86_64,i686 | jq -cR '{only: ., os: "ubuntu-latest"}' \ && cibuildwheel --print-build-identifiers --platform linux --arch aarch64 | jq -cR '{only: ., os: "arm-4core-linux"}' \ - && cibuildwheel --print-build-identifiers --platform windows --arch AMD64,x86 | jq -cR '{only: ., os: "windows-latest"}' \ + && cibuildwheel --print-build-identifiers --platform windows --arch AMD64,x86 | grep -v 313 | jq -cR '{only: ., os: "windows-latest"}' \ && cibuildwheel --print-build-identifiers --platform macos --arch x86_64,universal2 | jq -cR '{only: ., os: "macos-13"}' } | jq -sc ) @@ -83,7 +83,7 @@ jobs: - name: Build wheels arm64 if: always() && matrix.os == 'arm-4core-linux' - run: /home/runner/.local/bin/pipx run cibuildwheel==2.16.5 --only ${{ matrix.only }} + run: /home/runner/.local/bin/pipx run cibuildwheel==2.22.0 --only ${{ matrix.only }} env: CIBW_SKIP: ${{ inputs.cibw_skip }} CIBW_PRERELEASE_PYTHONS: ${{ inputs.cibw_prerelease_pythons }} @@ -107,7 +107,7 @@ jobs: rm -rf ./tempwheelhouse CIBW_REPAIR_WHEEL_COMMAND_MACOS: | zip -d {wheel} \*.c \*.cpp \*.cc \*.h \*.hpp \*.pyx && - delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} + MACOSX_DEPLOYMENT_TARGET=12.7 delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: choco install -y 7zip && 7z d -r "{wheel}" *.c *.cpp *.cc *.h *.hpp *.pyx && @@ -117,7 +117,7 @@ jobs: - name: Build wheels if: always() && matrix.os != 'arm-4core-linux' - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.22.0 with: only: ${{ matrix.only }} env: @@ -143,7 +143,7 @@ jobs: rm -rf ./tempwheelhouse CIBW_REPAIR_WHEEL_COMMAND_MACOS: | zip -d {wheel} \*.c \*.cpp \*.cc \*.h \*.hpp \*.pyx && - delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} + MACOSX_DEPLOYMENT_TARGET=12.7 delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: choco install -y 7zip && 7z d -r "{wheel}" *.c *.cpp *.cc *.h *.hpp *.pyx && diff --git a/.github/workflows/generate-package-versions.yml b/.github/workflows/generate-package-versions.yml index 740edc20725..b8729e882c9 100644 --- a/.github/workflows/generate-package-versions.yml +++ b/.github/workflows/generate-package-versions.yml @@ -49,6 +49,11 @@ jobs: with: python-version: "3.12" + - name: Setup Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index 69400d35dbd..23a1c05a517 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -11,7 +11,7 @@ jobs: validate: name: Check requirements lockfiles runs-on: ubuntu-latest - container: ghcr.io/datadog/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 + container: ghcr.io/datadog/dd-trace-py/testrunner:0a50e839f4b1600f02157518b8d016451b346578@sha256:5dae9bc7872f69b31b612690f0748c7ad71ab90ef28a754b2ae93d0ba505837b steps: - uses: actions/checkout@v4 with: @@ -23,7 +23,7 @@ jobs: run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Set python interpreters - run: pyenv global 3.10 3.7 3.8 3.9 3.11 3.12 + run: pyenv global 3.10 3.7 3.8 3.9 3.11 3.12 3.13 - name: Install Dependencies run: pip install --upgrade pip && pip install riot==0.20.1 diff --git a/.gitlab/download-dependency-wheels.sh b/.gitlab/download-dependency-wheels.sh index 431e662e4c7..c80c60af07b 100755 --- a/.gitlab/download-dependency-wheels.sh +++ b/.gitlab/download-dependency-wheels.sh @@ -20,7 +20,7 @@ export PYTHONUNBUFFERED=TRUE --local-ddtrace \ --arch x86_64 \ --arch aarch64 \ - --platform musllinux_1_1 \ + --platform musllinux_1_2 \ --platform manylinux2014 \ --output-dir ../pywheels-dep \ --verbose diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 74d76bc0ae4..0cf300d7cbd 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -1,3 +1,22 @@ +build_base_venvs: + extends: .testrunner + stage: package + parallel: + matrix: + - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + variables: + CMAKE_BUILD_PARALLEL_LEVEL: 12 + PIP_VERBOSE: 1 + script: + - pip install riot==0.20.0 + - riot -P -v generate --python=$PYTHON_VERSION + artifacts: + name: venv_$PYTHON_VERSION + paths: + - .riot/venv_* + - ddtrace/**/*.so* + - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* + download_ddtrace_artifacts: image: registry.ddbuild.io/github-cli:v27480869-eafb11d-2.43.0 tags: [ "arch:amd64" ] @@ -31,6 +50,8 @@ download_dependency_wheels: PYTHON_VERSION: "3.11" - PYTHON_IMAGE_TAG: "3.12.0" PYTHON_VERSION: "3.12" + - PYTHON_IMAGE_TAG: "3.13.0" + PYTHON_VERSION: "3.13" script: - .gitlab/download-dependency-wheels.sh artifacts: diff --git a/.gitlab/testrunner.yml b/.gitlab/testrunner.yml index f1fd4806506..fe9fb34bec6 100644 --- a/.gitlab/testrunner.yml +++ b/.gitlab/testrunner.yml @@ -1,9 +1,9 @@ .testrunner: - image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:0a50e839f4b1600f02157518b8d016451b346578@sha256:5dae9bc7872f69b31b612690f0748c7ad71ab90ef28a754b2ae93d0ba505837b # DEV: we have a larger pool of amd64 runners, prefer that over arm64 tags: [ "arch:amd64" ] timeout: 20m before_script: - ulimit -c unlimited - - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev + - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13 - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index 4495c6fa6a6..ce1fb8fd0ad 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -11,12 +11,12 @@ variables: # CI_DEBUG_SERVICES: "true" .testrunner: - image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 + image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:0a50e839f4b1600f02157518b8d016451b346578@sha256:5dae9bc7872f69b31b612690f0748c7ad71ab90ef28a754b2ae93d0ba505837b # DEV: we have a larger pool of amd64 runners, prefer that over arm64 tags: [ "arch:amd64" ] timeout: 20m before_script: - - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13-dev + - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13 - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ @@ -62,7 +62,7 @@ build_base_venvs: stage: riot parallel: matrix: - - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] variables: CMAKE_BUILD_PARALLEL_LEVEL: 12 PIP_VERBOSE: 1 diff --git a/.riot/requirements/102dfdd.txt b/.riot/requirements/102dfdd.txt new file mode 100644 index 00000000000..40bf3c75049 --- /dev/null +++ b/.riot/requirements/102dfdd.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/102dfdd.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +structlog==20.2.0 diff --git a/.riot/requirements/104daf8.txt b/.riot/requirements/104daf8.txt new file mode 100644 index 00000000000..e25e2cb84d2 --- /dev/null +++ b/.riot/requirements/104daf8.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/104daf8.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opensearch-py[requests]==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/104f450.txt b/.riot/requirements/104f450.txt new file mode 100644 index 00000000000..a9bf25ae538 --- /dev/null +++ b/.riot/requirements/104f450.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/104f450.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +logbook==1.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1053dce.txt b/.riot/requirements/1053dce.txt new file mode 100644 index 00000000000..5b1c1d31dbe --- /dev/null +++ b/.riot/requirements/1053dce.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1053dce.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/114bad8.txt b/.riot/requirements/114bad8.txt new file mode 100644 index 00000000000..27a7f4e24f7 --- /dev/null +++ b/.riot/requirements/114bad8.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/114bad8.in +# +attrs==24.2.0 +blinker==1.8.2 +click==8.1.7 +coverage[toml]==7.6.1 +flask==3.0.3 +flask-caching==1.10.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-memcached==1.62 +redis==5.1.1 +sortedcontainers==2.4.0 +werkzeug==3.0.4 diff --git a/.riot/requirements/11f2bd0.txt b/.riot/requirements/11f2bd0.txt new file mode 100644 index 00000000000..fdab5d63d33 --- /dev/null +++ b/.riot/requirements/11f2bd0.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/11f2bd0.in +# +annotated-types==0.7.0 +attrs==24.2.0 +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +coverage[toml]==7.6.1 +flask==2.3.3 +flask-openapi3==4.0.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 +urllib3==1.26.20 +werkzeug==2.3.8 +zipp==3.20.2 diff --git a/.riot/requirements/11fd02a.txt b/.riot/requirements/11fd02a.txt new file mode 100644 index 00000000000..c00ae722bbb --- /dev/null +++ b/.riot/requirements/11fd02a.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/11fd02a.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1261ed3.txt b/.riot/requirements/1261ed3.txt new file mode 100644 index 00000000000..cf97c1bc502 --- /dev/null +++ b/.riot/requirements/1261ed3.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1261ed3.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiohttp-jinja2==1.5.1 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/1304e20.txt b/.riot/requirements/1304e20.txt new file mode 100644 index 00000000000..54f718e4122 --- /dev/null +++ b/.riot/requirements/1304e20.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1304e20.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==4.2.16 +django-configurations==2.5.1 +djangorestframework==3.15.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 diff --git a/.riot/requirements/1332b9d.txt b/.riot/requirements/1332b9d.txt new file mode 100644 index 00000000000..49dced5d336 --- /dev/null +++ b/.riot/requirements/1332b9d.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1332b9d.in +# +asn1crypto==1.5.1 +attrs==24.2.0 +certifi==2024.8.30 +cffi==1.17.1 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +cryptography==38.0.4 +filelock==3.16.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +platformdirs==4.3.6 +pluggy==1.5.0 +pycparser==2.22 +pyjwt==2.9.0 +pyopenssl==23.2.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +pytz==2024.2 +requests==2.32.3 +responses==0.16.0 +six==1.16.0 +snowflake-connector-python==3.12.2 +sortedcontainers==2.4.0 +tomlkit==0.13.2 +typing-extensions==4.12.2 +urllib3==2.2.3 diff --git a/.riot/requirements/13658ae.txt b/.riot/requirements/13658ae.txt new file mode 100644 index 00000000000..e4ac641af5c --- /dev/null +++ b/.riot/requirements/13658ae.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13658ae.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch==8.15.1 +elasticsearch7==7.17.12 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/136fddd.txt b/.riot/requirements/136fddd.txt new file mode 100644 index 00000000000..848b88850d0 --- /dev/null +++ b/.riot/requirements/136fddd.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/136fddd.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +elasticsearch5==5.5.6 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/1374394.txt b/.riot/requirements/1374394.txt new file mode 100644 index 00000000000..9e287a285b0 --- /dev/null +++ b/.riot/requirements/1374394.txt @@ -0,0 +1,34 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1374394.in +# +astunparse==1.6.3 +attrs==24.2.0 +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +coverage[toml]==7.6.1 +flask==3.0.3 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +six==1.16.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 +virtualenv-clone==0.5.7 +werkzeug==3.0.4 +wheel==0.44.0 diff --git a/.riot/requirements/1381214.txt b/.riot/requirements/1381214.txt new file mode 100644 index 00000000000..583f505bac4 --- /dev/null +++ b/.riot/requirements/1381214.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1381214.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +dramatiq==1.17.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +prometheus-client==0.21.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +redis==5.1.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/13ae267.txt b/.riot/requirements/13ae267.txt new file mode 100644 index 00000000000..72f91d44446 --- /dev/null +++ b/.riot/requirements/13ae267.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13ae267.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +loguru==0.7.2 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/141bfd1.txt b/.riot/requirements/141bfd1.txt new file mode 100644 index 00000000000..ca6a38880e2 --- /dev/null +++ b/.riot/requirements/141bfd1.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/141bfd1.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==7.1.2 +coverage[toml]==7.6.1 +flask==1.1.4 +gunicorn==23.0.0 +httpretty==1.0.5 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +itsdangerous==1.1.0 +jinja2==2.11.3 +markupsafe==1.1.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.2.3 +werkzeug==1.0.1 diff --git a/.riot/requirements/141f7eb.txt b/.riot/requirements/141f7eb.txt new file mode 100644 index 00000000000..d8494646e5d --- /dev/null +++ b/.riot/requirements/141f7eb.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/141f7eb.in +# +attrs==24.2.0 +cattrs==22.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +molten==1.0.2 +mypy-extensions==1.0.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +typing-extensions==3.10.0.2 +typing-inspect==0.6.0 diff --git a/.riot/requirements/1463930.txt b/.riot/requirements/1463930.txt new file mode 100644 index 00000000000..313484f83ce --- /dev/null +++ b/.riot/requirements/1463930.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1463930.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.0.8 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/14be2f6.txt b/.riot/requirements/14be2f6.txt new file mode 100644 index 00000000000..0a516b36c05 --- /dev/null +++ b/.riot/requirements/14be2f6.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/14be2f6.in +# +algoliasearch==2.6.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/14d7e8a.txt b/.riot/requirements/14d7e8a.txt new file mode 100644 index 00000000000..979467f1e35 --- /dev/null +++ b/.riot/requirements/14d7e8a.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/14d7e8a.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiohttp-jinja2==1.6 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/14f1594.txt b/.riot/requirements/14f1594.txt new file mode 100644 index 00000000000..16c4e6c559a --- /dev/null +++ b/.riot/requirements/14f1594.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/14f1594.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mongoengine==0.29.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymongo==3.12.3 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/152e97f.txt b/.riot/requirements/152e97f.txt new file mode 100644 index 00000000000..973e252ab4f --- /dev/null +++ b/.riot/requirements/152e97f.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/152e97f.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +elasticsearch6==6.8.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/1584f8c.txt b/.riot/requirements/1584f8c.txt new file mode 100644 index 00000000000..602372e9b06 --- /dev/null +++ b/.riot/requirements/1584f8c.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1584f8c.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==4.2.16 +django-configurations==2.5.1 +django-hosts==6.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/164c3ce.txt b/.riot/requirements/164c3ce.txt new file mode 100644 index 00000000000..5acfc83a32e --- /dev/null +++ b/.riot/requirements/164c3ce.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/164c3ce.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiohttp-jinja2==1.5.1 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/167b853.txt b/.riot/requirements/167b853.txt new file mode 100644 index 00000000000..71aa1ae2587 --- /dev/null +++ b/.riot/requirements/167b853.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/167b853.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/16acf84.txt b/.riot/requirements/16acf84.txt new file mode 100644 index 00000000000..402495f9654 --- /dev/null +++ b/.riot/requirements/16acf84.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16acf84.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==3.2.25 +django-configurations==2.5.1 +djangorestframework==3.11.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +pytz==2024.2 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 diff --git a/.riot/requirements/16cc321.txt b/.riot/requirements/16cc321.txt new file mode 100644 index 00000000000..e46e05ff1bb --- /dev/null +++ b/.riot/requirements/16cc321.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16cc321.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/16d2d1f.txt b/.riot/requirements/16d2d1f.txt new file mode 100644 index 00000000000..7092a5762ac --- /dev/null +++ b/.riot/requirements/16d2d1f.txt @@ -0,0 +1,48 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16d2d1f.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==2.1.1 +click==8.1.7 +coverage[toml]==7.6.1 +deprecated==1.2.14 +flask==2.1.3 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.0.1 +mock==5.1.0 +opentelemetry-api==1.15.0 +opentelemetry-instrumentation==0.45b0 +opentelemetry-instrumentation-flask==0.45b0 +opentelemetry-instrumentation-wsgi==0.45b0 +opentelemetry-semantic-conventions==0.45b0 +opentelemetry-util-http==0.45b0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.28.1 +sortedcontainers==2.4.0 +urllib3==1.26.20 +werkzeug==2.1.2 +wrapt==1.16.0 +zipp==3.20.2 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/16de9c4.txt b/.riot/requirements/16de9c4.txt new file mode 100644 index 00000000000..ed357be4e45 --- /dev/null +++ b/.riot/requirements/16de9c4.txt @@ -0,0 +1,37 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16de9c4.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiosignal==1.3.1 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch[async]==8.15.1 +elasticsearch7[async]==7.17.12 +events==0.5 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.1.0 +opensearch-py[async]==2.7.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +six==1.16.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 +yarl==1.13.1 diff --git a/.riot/requirements/178f7d5.txt b/.riot/requirements/178f7d5.txt new file mode 100644 index 00000000000..4d7d3e5b6e6 --- /dev/null +++ b/.riot/requirements/178f7d5.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/178f7d5.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +logbook==1.7.0.post0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/17d40ef.txt b/.riot/requirements/17d40ef.txt new file mode 100644 index 00000000000..53c94aadbe1 --- /dev/null +++ b/.riot/requirements/17d40ef.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/17d40ef.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +loguru==0.4.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1819cb6.txt b/.riot/requirements/1819cb6.txt new file mode 100644 index 00000000000..0c9e45ced2c --- /dev/null +++ b/.riot/requirements/1819cb6.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1819cb6.in +# +attrs==24.2.0 +blinker==1.8.2 +click==7.1.2 +coverage[toml]==7.6.1 +flask==1.1.4 +flask-caching==1.10.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +itsdangerous==1.1.0 +jinja2==2.11.3 +markupsafe==1.1.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-memcached==1.62 +redis==5.1.1 +sortedcontainers==2.4.0 +werkzeug==1.0.1 diff --git a/.riot/requirements/188244e.txt b/.riot/requirements/188244e.txt new file mode 100644 index 00000000000..7a30a1a4b8e --- /dev/null +++ b/.riot/requirements/188244e.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/188244e.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/18c6e70.txt b/.riot/requirements/18c6e70.txt new file mode 100644 index 00000000000..f257d8ded2b --- /dev/null +++ b/.riot/requirements/18c6e70.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18c6e70.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/18e9526.txt b/.riot/requirements/18e9526.txt new file mode 100644 index 00000000000..ce6bddab69f --- /dev/null +++ b/.riot/requirements/18e9526.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18e9526.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +events==0.5 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opensearch-py[requests]==2.7.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +six==1.16.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/192c7c0.txt b/.riot/requirements/192c7c0.txt new file mode 100644 index 00000000000..15f53062f83 --- /dev/null +++ b/.riot/requirements/192c7c0.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/192c7c0.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elasticsearch==7.17.12 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/19bbf6d.txt b/.riot/requirements/19bbf6d.txt new file mode 100644 index 00000000000..1e31a198638 --- /dev/null +++ b/.riot/requirements/19bbf6d.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/19bbf6d.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +dnspython==2.7.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mongoengine==0.29.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymongo==4.10.1 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1a485c9.txt b/.riot/requirements/1a485c9.txt new file mode 100644 index 00000000000..558f2540488 --- /dev/null +++ b/.riot/requirements/1a485c9.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a485c9.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +decorator==5.1.1 +dogpile-cache==1.3.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pbr==6.1.0 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +stevedore==5.3.0 diff --git a/.riot/requirements/1a508dc.txt b/.riot/requirements/1a508dc.txt new file mode 100644 index 00000000000..6e2dfecef5e --- /dev/null +++ b/.riot/requirements/1a508dc.txt @@ -0,0 +1,30 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a508dc.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==3.2.25 +django-configurations==2.5.1 +django-hosts==4.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +pytz==2024.2 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/1acabe0.txt b/.riot/requirements/1acabe0.txt new file mode 100644 index 00000000000..0f106bcd2dc --- /dev/null +++ b/.riot/requirements/1acabe0.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1acabe0.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1ada88e.txt b/.riot/requirements/1ada88e.txt new file mode 100644 index 00000000000..5fc0aa5664d --- /dev/null +++ b/.riot/requirements/1ada88e.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ada88e.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==4.2.16 +django-configurations==2.5.1 +django-hosts==5.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/1aed5dc.txt b/.riot/requirements/1aed5dc.txt new file mode 100644 index 00000000000..4d8f8858d78 --- /dev/null +++ b/.riot/requirements/1aed5dc.txt @@ -0,0 +1,30 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1aed5dc.in +# +attrs==24.2.0 +blinker==1.8.2 +cachelib==0.9.0 +click==7.1.2 +coverage[toml]==7.6.1 +flask==1.1.4 +flask-caching==2.3.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +itsdangerous==1.1.0 +jinja2==2.11.3 +markupsafe==1.1.1 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-memcached==1.62 +redis==5.1.1 +sortedcontainers==2.4.0 +werkzeug==1.0.1 diff --git a/.riot/requirements/1b86c06.txt b/.riot/requirements/1b86c06.txt new file mode 100644 index 00000000000..68de1371257 --- /dev/null +++ b/.riot/requirements/1b86c06.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b86c06.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==0.12.3 +httpx==0.17.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +rfc3986[idna2008]==1.5.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1b8d922.txt b/.riot/requirements/1b8d922.txt new file mode 100644 index 00000000000..76a225cb035 --- /dev/null +++ b/.riot/requirements/1b8d922.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b8d922.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mako==1.1.6 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1ba390a.txt b/.riot/requirements/1ba390a.txt new file mode 100644 index 00000000000..71d341c1fbb --- /dev/null +++ b/.riot/requirements/1ba390a.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ba390a.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +decorator==5.1.1 +dogpile-cache==0.9.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1bf4d76.txt b/.riot/requirements/1bf4d76.txt new file mode 100644 index 00000000000..be2efe8e43c --- /dev/null +++ b/.riot/requirements/1bf4d76.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bf4d76.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +decorator==5.1.1 +dogpile-cache==1.3.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pbr==6.1.0 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +stevedore==5.3.0 diff --git a/.riot/requirements/1c22cf9.txt b/.riot/requirements/1c22cf9.txt new file mode 100644 index 00000000000..091cd98d529 --- /dev/null +++ b/.riot/requirements/1c22cf9.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c22cf9.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pylibmc==1.6.3 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1cb554e.txt b/.riot/requirements/1cb554e.txt new file mode 100644 index 00000000000..27f518b59cc --- /dev/null +++ b/.riot/requirements/1cb554e.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1cb554e.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymemcache==3.4.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1ce0711.txt b/.riot/requirements/1ce0711.txt new file mode 100644 index 00000000000..6721b5e5b0b --- /dev/null +++ b/.riot/requirements/1ce0711.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ce0711.in +# +attrs==24.2.0 +beautifulsoup4==4.12.3 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +soupsieve==2.6 +waitress==3.0.0 +webob==1.8.8 +webtest==3.0.1 diff --git a/.riot/requirements/1ce93b3.txt b/.riot/requirements/1ce93b3.txt new file mode 100644 index 00000000000..a0edba9ffd0 --- /dev/null +++ b/.riot/requirements/1ce93b3.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ce93b3.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +dnspython==2.7.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mongoengine==0.29.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymongo==4.8.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d74d67.txt b/.riot/requirements/1d74d67.txt new file mode 100644 index 00000000000..32873cff656 --- /dev/null +++ b/.riot/requirements/1d74d67.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d74d67.in +# +aniso8601==9.0.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +graphene==3.3 +graphql-core==3.2.4 +graphql-relay==3.2.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d8a93c.txt b/.riot/requirements/1d8a93c.txt new file mode 100644 index 00000000000..54f5d2a96c9 --- /dev/null +++ b/.riot/requirements/1d8a93c.txt @@ -0,0 +1,48 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d8a93c.in +# +aiosqlite==0.17.0 +annotated-types==0.7.0 +attrs==24.2.0 +blinker==1.8.2 +bytecode==0.15.1 +cattrs==22.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +coverage[toml]==7.6.1 +envier==0.5.2 +flask==3.0.3 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +iso8601==1.1.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +peewee==3.17.6 +pluggy==1.5.0 +pony==0.7.19 +protobuf==5.28.2 +pycryptodome==3.21.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pypika-tortoise==0.1.6 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytz==2024.2 +requests==2.32.3 +sortedcontainers==2.4.0 +sqlalchemy==2.0.35 +tortoise-orm==0.21.6 +typing-extensions==4.12.2 +urllib3==2.2.3 +werkzeug==3.0.4 +xmltodict==0.13.0 diff --git a/.riot/requirements/1dd5678.txt b/.riot/requirements/1dd5678.txt new file mode 100644 index 00000000000..c3ed6ec2447 --- /dev/null +++ b/.riot/requirements/1dd5678.txt @@ -0,0 +1,30 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dd5678.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +gevent==24.2.1 +greenlet==3.1.1 +httpretty==1.1.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pyfakefs==5.6.0 +pytest==8.3.3 +pytest-asyncio==0.23.8 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-json-logger==2.0.7 +sortedcontainers==2.4.0 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/15e6ff4.txt b/.riot/requirements/1df4764.txt similarity index 65% rename from .riot/requirements/15e6ff4.txt rename to .riot/requirements/1df4764.txt index 205310cd885..d6cef24569d 100644 --- a/.riot/requirements/15e6ff4.txt +++ b/.riot/requirements/1df4764.txt @@ -1,21 +1,21 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15e6ff4.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1df4764.in # annotated-types==0.7.0 -anyio==4.6.2.post1 +anyio==4.7.0 attrs==24.2.0 -boto3==1.35.62 -botocore==1.35.62 +boto3==1.35.78 +botocore==1.35.78 certifi==2024.8.30 -coverage[toml]==7.6.7 -fastapi==0.115.5 +coverage[toml]==7.6.9 +fastapi==0.115.6 h11==0.14.0 httpcore==1.0.7 httpretty==1.1.4 -httpx==0.27.2 +httpx==0.28.1 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 @@ -25,22 +25,22 @@ msgpack==1.1.0 opentracing==2.4.0 packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 -s3transfer==0.10.3 -six==1.16.0 +s3transfer==0.10.4 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.41.2 +starlette==0.41.3 structlog==24.4.0 typing-extensions==4.12.2 urllib3==2.2.3 -wheel==0.45.0 +wheel==0.45.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.5.0 +setuptools==75.6.0 diff --git a/.riot/requirements/1e19c17.txt b/.riot/requirements/1e19c17.txt new file mode 100644 index 00000000000..615658928e1 --- /dev/null +++ b/.riot/requirements/1e19c17.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e19c17.in +# +anyio==4.6.0 +asgiref==3.0.0 +async-timeout==3.0.1 +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e4bb51.txt b/.riot/requirements/1e4bb51.txt new file mode 100644 index 00000000000..c160a2df5e6 --- /dev/null +++ b/.riot/requirements/1e4bb51.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e4bb51.in +# +aniso8601==9.0.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +graphene==3.0 +graphql-core==3.1.7 +graphql-relay==3.1.5 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e4dfe1.txt b/.riot/requirements/1e4dfe1.txt new file mode 100644 index 00000000000..11f08da5171 --- /dev/null +++ b/.riot/requirements/1e4dfe1.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e4dfe1.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/1e659c4.txt b/.riot/requirements/1e659c4.txt new file mode 100644 index 00000000000..ef8e4a09e09 --- /dev/null +++ b/.riot/requirements/1e659c4.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e659c4.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymemcache==4.0.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e70094.txt b/.riot/requirements/1e70094.txt new file mode 100644 index 00000000000..ac90db74765 --- /dev/null +++ b/.riot/requirements/1e70094.txt @@ -0,0 +1,42 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e70094.in +# +attrs==24.2.0 +beautifulsoup4==4.12.3 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +hupper==1.12.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pastedeploy==3.1.0 +plaster==1.1.2 +plaster-pastedeploy==1.0.1 +pluggy==1.5.0 +pserve-test-app @ file:///root/project/tests/contrib/pyramid/pserve_app +pyramid==2.0.2 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +soupsieve==2.6 +translationstring==1.4 +urllib3==2.2.3 +venusian==3.1.0 +waitress==3.0.0 +webob==1.8.8 +webtest==3.0.1 +zope-deprecation==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/1ebb239.txt b/.riot/requirements/1ebb239.txt new file mode 100644 index 00000000000..baa97737f91 --- /dev/null +++ b/.riot/requirements/1ebb239.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ebb239.in +# +attrs==24.2.0 +autocommand==2.2.2 +cheroot==10.0.1 +cherrypy==18.10.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +jaraco-collections==5.1.0 +jaraco-context==6.0.1 +jaraco-functools==4.1.0 +jaraco-text==4.0.0 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +portend==3.2.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +six==1.16.0 +sortedcontainers==2.4.0 +tempora==5.7.0 +zc-lockfile==3.0.post1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/1ec9462.txt b/.riot/requirements/1ec9462.txt new file mode 100644 index 00000000000..da918b276a7 --- /dev/null +++ b/.riot/requirements/1ec9462.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ec9462.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.0.0 diff --git a/.riot/requirements/1f3b209.txt b/.riot/requirements/1f3b209.txt new file mode 100644 index 00000000000..ed48c26f9b8 --- /dev/null +++ b/.riot/requirements/1f3b209.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f3b209.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mariadb==1.1.10 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1fa3005.txt b/.riot/requirements/1fa3005.txt new file mode 100644 index 00000000000..d05c2537930 --- /dev/null +++ b/.riot/requirements/1fa3005.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1fa3005.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +jinja2==3.0.3 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1fc9ecc.txt b/.riot/requirements/1fc9ecc.txt new file mode 100644 index 00000000000..f4245743dde --- /dev/null +++ b/.riot/requirements/1fc9ecc.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1fc9ecc.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mariadb==1.1.10 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1fe8dd2.txt b/.riot/requirements/1fe8dd2.txt new file mode 100644 index 00000000000..c6356e47072 --- /dev/null +++ b/.riot/requirements/1fe8dd2.txt @@ -0,0 +1,83 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1fe8dd2.in +# +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.7.0 +appdirs==1.4.4 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +coverage[toml]==7.6.9 +dataclasses-json==0.6.7 +datasets==3.2.0 +dill==0.3.8 +distro==1.9.0 +filelock==3.16.1 +frozenlist==1.5.0 +fsspec[http]==2024.9.0 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.1 +huggingface-hub==0.26.5 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jiter==0.8.2 +jsonpatch==1.33 +jsonpointer==3.0.0 +langchain==0.2.17 +langchain-community==0.2.19 +langchain-core==0.2.43 +langchain-openai==0.1.25 +langchain-text-splitters==0.2.4 +langsmith==0.1.147 +marshmallow==3.23.1 +mock==5.1.0 +multidict==6.1.0 +multiprocess==0.70.16 +mypy-extensions==1.0.0 +nest-asyncio==1.6.0 +numpy==1.26.4 +openai==1.57.2 +opentracing==2.4.0 +orjson==3.10.12 +packaging==24.2 +pandas==2.2.3 +pluggy==1.5.0 +propcache==0.2.1 +pyarrow==18.1.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pysbd==0.3.4 +pytest==8.3.4 +pytest-asyncio==0.21.1 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +pyyaml==6.0.2 +ragas==0.1.21 +regex==2024.11.6 +requests==2.32.3 +requests-toolbelt==1.0.0 +six==1.17.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tenacity==8.5.0 +tiktoken==0.8.0 +tqdm==4.67.1 +typing-extensions==4.12.2 +typing-inspect==0.9.0 +tzdata==2024.2 +urllib3==2.2.3 +vcrpy==6.0.2 +wrapt==1.17.0 +xxhash==3.5.0 +yarl==1.18.3 diff --git a/.riot/requirements/248da41.txt b/.riot/requirements/248da41.txt new file mode 100644 index 00000000000..34d903b5cbf --- /dev/null +++ b/.riot/requirements/248da41.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/248da41.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +docker==7.1.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/2538ed0.txt b/.riot/requirements/2538ed0.txt new file mode 100644 index 00000000000..f3d631a3ba0 --- /dev/null +++ b/.riot/requirements/2538ed0.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2538ed0.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch==8.0.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/2581b3a.txt b/.riot/requirements/2581b3a.txt new file mode 100644 index 00000000000..b0fbf422fae --- /dev/null +++ b/.riot/requirements/2581b3a.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2581b3a.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.0.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/2644218.txt b/.riot/requirements/2644218.txt new file mode 100644 index 00000000000..0af7a95877a --- /dev/null +++ b/.riot/requirements/2644218.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2644218.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +httpretty==1.1.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 diff --git a/.riot/requirements/27d0ff8.txt b/.riot/requirements/27d0ff8.txt new file mode 100644 index 00000000000..291fe50cacc --- /dev/null +++ b/.riot/requirements/27d0ff8.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/27d0ff8.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mako==1.3.5 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/27e3d7b.txt b/.riot/requirements/27e3d7b.txt new file mode 100644 index 00000000000..602a0f0c52d --- /dev/null +++ b/.riot/requirements/27e3d7b.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/27e3d7b.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +graphql-core==3.2.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/2d6c3d0.txt b/.riot/requirements/2d6c3d0.txt new file mode 100644 index 00000000000..a2b00eb5c7c --- /dev/null +++ b/.riot/requirements/2d6c3d0.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2d6c3d0.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/2dd0811.txt b/.riot/requirements/2dd0811.txt new file mode 100644 index 00000000000..ecd42e076bd --- /dev/null +++ b/.riot/requirements/2dd0811.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2dd0811.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +graphql-core==3.2.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/3ab519c.txt b/.riot/requirements/3ab519c.txt new file mode 100644 index 00000000000..fd80ad8e698 --- /dev/null +++ b/.riot/requirements/3ab519c.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3ab519c.in +# +anyio==4.6.0 +asgiref==3.8.1 +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/3b804dc.txt b/.riot/requirements/3b804dc.txt new file mode 100644 index 00000000000..aa60e7c9491 --- /dev/null +++ b/.riot/requirements/3b804dc.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3b804dc.in +# +anyio==4.6.0 +asgiref==3.8.1 +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/3c3f295.txt b/.riot/requirements/3c3f295.txt new file mode 100644 index 00000000000..c97658e408e --- /dev/null +++ b/.riot/requirements/3c3f295.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3c3f295.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch8==8.0.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/3dd53da.txt b/.riot/requirements/3dd53da.txt new file mode 100644 index 00000000000..088ac0ddd7e --- /dev/null +++ b/.riot/requirements/3dd53da.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3dd53da.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +dnspython==2.7.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mongoengine==0.29.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymongo==4.8.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/3f1be84.txt b/.riot/requirements/3f1be84.txt new file mode 100644 index 00000000000..fb754701b3b --- /dev/null +++ b/.riot/requirements/3f1be84.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3f1be84.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch8==8.15.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/1edf426.txt b/.riot/requirements/4132bce.txt similarity index 70% rename from .riot/requirements/1edf426.txt rename to .riot/requirements/4132bce.txt index 56a5eb28b4d..b27023913a3 100644 --- a/.riot/requirements/1edf426.txt +++ b/.riot/requirements/4132bce.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1edf426.in +# pip-compile --no-annotate .riot/requirements/4132bce.in # attrs==24.2.0 -coverage[toml]==7.6.4 -gevent==24.11.1 +coverage[toml]==7.6.9 +gevent==23.9.1 greenlet==3.1.1 hypothesis==6.45.0 iniconfig==2.0.0 @@ -14,13 +14,13 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 +pytest==8.3.4 pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.16.0 sortedcontainers==2.4.0 zope-event==5.0 -zope-interface==7.1.1 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 +# setuptools diff --git a/.riot/requirements/44eeaa9.txt b/.riot/requirements/44eeaa9.txt new file mode 100644 index 00000000000..138f4161595 --- /dev/null +++ b/.riot/requirements/44eeaa9.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/44eeaa9.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/4fd1520.txt b/.riot/requirements/4fd1520.txt new file mode 100644 index 00000000000..88c1fc5703a --- /dev/null +++ b/.riot/requirements/4fd1520.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4fd1520.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +decorator==5.1.1 +dogpile-cache==1.3.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pbr==6.1.0 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +stevedore==5.3.0 diff --git a/.riot/requirements/5b922fc.txt b/.riot/requirements/5b922fc.txt new file mode 100644 index 00000000000..ff7fa5e6ba6 --- /dev/null +++ b/.riot/requirements/5b922fc.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/5b922fc.in +# +asgiref==3.8.1 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==2.1.1 +click==7.1.2 +coverage[toml]==7.6.1 +flask==1.1.4 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +itsdangerous==1.1.0 +jinja2==2.11.3 +markupsafe==2.0.1 +mock==5.1.0 +opentelemetry-api==1.0.0 +opentelemetry-instrumentation==0.19b0 +opentelemetry-instrumentation-flask==0.19b0 +opentelemetry-instrumentation-wsgi==0.19b0 +opentelemetry-util-http==0.19b0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.28.1 +sortedcontainers==2.4.0 +urllib3==1.26.20 +werkzeug==1.0.1 +wrapt==1.16.0 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/6cf373b.txt b/.riot/requirements/6cf373b.txt new file mode 100644 index 00000000000..e69fda1f1ed --- /dev/null +++ b/.riot/requirements/6cf373b.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6cf373b.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/70e034f.txt b/.riot/requirements/70e034f.txt new file mode 100644 index 00000000000..12950d5019e --- /dev/null +++ b/.riot/requirements/70e034f.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/70e034f.in +# +attrs==24.2.0 +cattrs==22.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +molten==1.0.2 +mypy-extensions==1.0.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +typing-extensions==3.10.0.2 +typing-inspect==0.6.0 diff --git a/.riot/requirements/74ccb83.txt b/.riot/requirements/74ccb83.txt new file mode 100644 index 00000000000..9a3462b41cd --- /dev/null +++ b/.riot/requirements/74ccb83.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/74ccb83.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/788c304.txt b/.riot/requirements/788c304.txt new file mode 100644 index 00000000000..36e1cd013d9 --- /dev/null +++ b/.riot/requirements/788c304.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/788c304.in +# +anyio==4.6.0 +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/7a40e08.txt b/.riot/requirements/7a40e08.txt new file mode 100644 index 00000000000..a770877b6ee --- /dev/null +++ b/.riot/requirements/7a40e08.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/7a40e08.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elasticsearch7==7.13.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/921bc6c.txt b/.riot/requirements/7bbf828.txt similarity index 65% rename from .riot/requirements/921bc6c.txt rename to .riot/requirements/7bbf828.txt index fd44244070f..e1c39713bce 100644 --- a/.riot/requirements/921bc6c.txt +++ b/.riot/requirements/7bbf828.txt @@ -1,21 +1,21 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/921bc6c.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/7bbf828.in # annotated-types==0.7.0 -anyio==4.6.2.post1 +anyio==4.7.0 attrs==24.2.0 -boto3==1.35.62 -botocore==1.35.62 +boto3==1.35.78 +botocore==1.35.78 certifi==2024.8.30 -coverage[toml]==7.6.7 -fastapi==0.115.5 +coverage[toml]==7.6.9 +fastapi==0.115.6 h11==0.14.0 httpcore==1.0.7 httpretty==1.1.4 -httpx==0.27.2 +httpx==0.28.1 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 @@ -25,22 +25,22 @@ msgpack==1.1.0 opentracing==2.4.0 packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 -s3transfer==0.10.3 -six==1.16.0 +s3transfer==0.10.4 +six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.41.2 +starlette==0.41.3 structlog==24.4.0 typing-extensions==4.12.2 urllib3==2.2.3 -wheel==0.45.0 +wheel==0.45.1 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.5.0 +setuptools==75.6.0 diff --git a/.riot/requirements/8ce955f.txt b/.riot/requirements/8ce955f.txt new file mode 100644 index 00000000000..6a3a0e63588 --- /dev/null +++ b/.riot/requirements/8ce955f.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/8ce955f.in +# +anyio==4.6.0 +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +h11==0.14.0 +httpcore==0.16.3 +httpx==0.23.3 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +rfc3986[idna2008]==1.5.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/91fe586.txt b/.riot/requirements/91fe586.txt new file mode 100644 index 00000000000..46d48acec17 --- /dev/null +++ b/.riot/requirements/91fe586.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/91fe586.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +requests-mock==1.12.1 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/9a07d4a.txt b/.riot/requirements/9a07d4a.txt new file mode 100644 index 00000000000..027306e2816 --- /dev/null +++ b/.riot/requirements/9a07d4a.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/9a07d4a.in +# +amqp==5.2.0 +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +kombu==5.4.2 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tzdata==2024.2 +vine==5.1.0 diff --git a/.riot/requirements/9a5c0d9.txt b/.riot/requirements/9a5c0d9.txt new file mode 100644 index 00000000000..edab275315a --- /dev/null +++ b/.riot/requirements/9a5c0d9.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/9a5c0d9.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +gevent==24.2.1 +greenlet==3.1.1 +gunicorn==23.0.0 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.2.3 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/a0cc2a4.txt b/.riot/requirements/a0cc2a4.txt new file mode 100644 index 00000000000..f724ecdac7a --- /dev/null +++ b/.riot/requirements/a0cc2a4.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a0cc2a4.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymemcache==3.5.2 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/a9f396a.txt b/.riot/requirements/a9f396a.txt new file mode 100644 index 00000000000..4505eee48b0 --- /dev/null +++ b/.riot/requirements/a9f396a.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a9f396a.in +# +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aiohttp-jinja2==1.6 +aiosignal==1.3.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +frozenlist==1.4.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +multidict==6.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-aiohttp==1.0.5 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +yarl==1.13.1 diff --git a/.riot/requirements/ae8bd25.txt b/.riot/requirements/ae8bd25.txt new file mode 100644 index 00000000000..f0736d28cfc --- /dev/null +++ b/.riot/requirements/ae8bd25.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ae8bd25.in +# +asgiref==3.8.1 +attrs==24.2.0 +coverage[toml]==7.6.1 +django==4.2.16 +django-configurations==2.5.1 +djangorestframework==3.15.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.5.1 diff --git a/.riot/requirements/b29075f.txt b/.riot/requirements/b29075f.txt new file mode 100644 index 00000000000..d070fd9e2f2 --- /dev/null +++ b/.riot/requirements/b29075f.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b29075f.in +# +annotated-types==0.7.0 +attrs==24.2.0 +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +coverage[toml]==7.6.1 +flask==3.0.3 +flask-openapi3==4.0.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 +urllib3==1.26.20 +werkzeug==3.0.4 +zipp==3.20.2 diff --git a/.riot/requirements/b403d9d.txt b/.riot/requirements/b403d9d.txt new file mode 100644 index 00000000000..1cb46c6afb0 --- /dev/null +++ b/.riot/requirements/b403d9d.txt @@ -0,0 +1,49 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b403d9d.in +# +aiobotocore==2.3.1 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.9 +aioitertools==0.12.0 +aiosignal==1.3.1 +attrs==24.2.0 +botocore==1.24.21 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch==8.15.1 +events==0.5 +frozenlist==1.4.1 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +multidict==6.1.0 +opensearch-py==2.7.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pynamodb==5.5.1 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +requests==2.32.3 +six==1.16.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 +wrapt==1.16.0 +yarl==1.13.1 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/bc64f49.txt b/.riot/requirements/bc64f49.txt new file mode 100644 index 00000000000..ab6f8840549 --- /dev/null +++ b/.riot/requirements/bc64f49.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bc64f49.in +# +attrs==24.2.0 +autocommand==2.2.2 +cheroot==10.0.1 +cherrypy==18.10.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +jaraco-collections==5.1.0 +jaraco-context==6.0.1 +jaraco-functools==4.1.0 +jaraco-text==4.0.0 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +portend==3.2.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +six==1.16.0 +sortedcontainers==2.4.0 +tempora==5.7.0 +zc-lockfile==3.0.post1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/bc7a1f4.txt b/.riot/requirements/bc7a1f4.txt new file mode 100644 index 00000000000..a73a0ac6da4 --- /dev/null +++ b/.riot/requirements/bc7a1f4.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bc7a1f4.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +elasticsearch1==1.10.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/bcbec2a.txt b/.riot/requirements/bcbec2a.txt new file mode 100644 index 00000000000..665c0aadc1a --- /dev/null +++ b/.riot/requirements/bcbec2a.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bcbec2a.in +# +annotated-types==0.7.0 +anyio==4.7.0 +attrs==24.2.0 +boto3==1.35.78 +botocore==1.35.78 +certifi==2024.8.30 +coverage[toml]==7.6.9 +fastapi==0.115.6 +h11==0.14.0 +httpcore==1.0.7 +httpretty==1.1.4 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +jmespath==1.0.1 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +s3transfer==0.10.4 +six==1.17.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.3 +structlog==24.4.0 +typing-extensions==4.12.2 +urllib3==2.2.3 +wheel==0.45.1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.6.0 diff --git a/.riot/requirements/bebdd41.txt b/.riot/requirements/bebdd41.txt new file mode 100644 index 00000000000..c0918e4e15a --- /dev/null +++ b/.riot/requirements/bebdd41.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bebdd41.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/c1351c9.txt b/.riot/requirements/c1351c9.txt new file mode 100644 index 00000000000..10e97c081a4 --- /dev/null +++ b/.riot/requirements/c1351c9.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c1351c9.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +redis==5.1.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/c4d4455.txt b/.riot/requirements/c4d4455.txt new file mode 100644 index 00000000000..1a8b9f970ef --- /dev/null +++ b/.riot/requirements/c4d4455.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c4d4455.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/c77bbb6.txt b/.riot/requirements/c77bbb6.txt new file mode 100644 index 00000000000..3f53bcba5e6 --- /dev/null +++ b/.riot/requirements/c77bbb6.txt @@ -0,0 +1,48 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c77bbb6.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==2.1.1 +click==8.1.7 +coverage[toml]==7.6.1 +deprecated==1.2.14 +flask==2.1.3 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.4.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.0.1 +mock==5.1.0 +opentelemetry-api==1.27.0 +opentelemetry-instrumentation==0.48b0 +opentelemetry-instrumentation-flask==0.48b0 +opentelemetry-instrumentation-wsgi==0.48b0 +opentelemetry-semantic-conventions==0.48b0 +opentelemetry-util-http==0.48b0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.28.1 +sortedcontainers==2.4.0 +urllib3==1.26.20 +werkzeug==2.1.2 +wrapt==1.16.0 +zipp==3.20.2 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/c8b476b.txt b/.riot/requirements/c8b476b.txt new file mode 100644 index 00000000000..d8fd4322d7f --- /dev/null +++ b/.riot/requirements/c8b476b.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c8b476b.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +gevent==24.2.1 +greenlet==3.1.1 +gunicorn==20.0.4 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.2.3 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/d5098dd.txt b/.riot/requirements/d5098dd.txt new file mode 100644 index 00000000000..bb4ade61f8a --- /dev/null +++ b/.riot/requirements/d5098dd.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d5098dd.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elasticsearch7==7.17.12 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/d7dfbc2.txt b/.riot/requirements/d7dfbc2.txt new file mode 100644 index 00000000000..2bee6eee691 --- /dev/null +++ b/.riot/requirements/d7dfbc2.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d7dfbc2.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +dnspython==2.7.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mongoengine==0.29.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pymongo==4.10.1 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/d81ad99.txt b/.riot/requirements/d81ad99.txt new file mode 100644 index 00000000000..3efb0a138c2 --- /dev/null +++ b/.riot/requirements/d81ad99.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d81ad99.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/db78045.txt b/.riot/requirements/db78045.txt new file mode 100644 index 00000000000..7a92cc52123 --- /dev/null +++ b/.riot/requirements/db78045.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/db78045.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +elasticsearch2==2.5.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/dbc6a48.txt b/.riot/requirements/dbc6a48.txt new file mode 100644 index 00000000000..e29a7f2eeee --- /dev/null +++ b/.riot/requirements/dbc6a48.txt @@ -0,0 +1,35 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/dbc6a48.in +# +amqp==5.3.1 +attrs==24.2.0 +billiard==4.2.1 +celery[redis]==5.4.0 +click==8.1.7 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +kombu==5.4.2 +mock==5.1.0 +more-itertools==8.10.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +prompt-toolkit==3.0.48 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +redis==5.2.1 +six==1.17.0 +sortedcontainers==2.4.0 +tzdata==2024.2 +vine==5.1.0 +wcwidth==0.2.13 diff --git a/.riot/requirements/dbeb1d7.txt b/.riot/requirements/dbeb1d7.txt new file mode 100644 index 00000000000..bbde6777f1c --- /dev/null +++ b/.riot/requirements/dbeb1d7.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/dbeb1d7.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/ddd8721.txt b/.riot/requirements/ddd8721.txt new file mode 100644 index 00000000000..baa4f15e9af --- /dev/null +++ b/.riot/requirements/ddd8721.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ddd8721.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/dedea98.txt b/.riot/requirements/dedea98.txt new file mode 100644 index 00000000000..dca66df78da --- /dev/null +++ b/.riot/requirements/dedea98.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/dedea98.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +structlog==24.4.0 diff --git a/.riot/requirements/df7a937.txt b/.riot/requirements/df7a937.txt new file mode 100644 index 00000000000..35a49fc7ae3 --- /dev/null +++ b/.riot/requirements/df7a937.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/df7a937.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/e06abee.txt b/.riot/requirements/e06abee.txt new file mode 100644 index 00000000000..e7be89f2738 --- /dev/null +++ b/.riot/requirements/e06abee.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e06abee.in +# +annotated-types==0.7.0 +attrs==24.2.0 +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.3.2 +click==8.1.7 +coverage[toml]==7.6.1 +flask==3.0.3 +flask-openapi3==4.0.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pydantic==2.9.2 +pydantic-core==2.23.4 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +typing-extensions==4.12.2 +urllib3==1.26.20 +werkzeug==3.0.4 +zipp==3.20.2 diff --git a/.riot/requirements/e20152c.txt b/.riot/requirements/e20152c.txt new file mode 100644 index 00000000000..3aeacecfdcd --- /dev/null +++ b/.riot/requirements/e20152c.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e20152c.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/e2bf559.txt b/.riot/requirements/e2bf559.txt new file mode 100644 index 00000000000..cef46e50c2d --- /dev/null +++ b/.riot/requirements/e2bf559.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e2bf559.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elastic-transport==8.15.0 +elasticsearch==8.15.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==2.2.3 diff --git a/.riot/requirements/ee48b16.txt b/.riot/requirements/ee48b16.txt new file mode 100644 index 00000000000..116921f222d --- /dev/null +++ b/.riot/requirements/ee48b16.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ee48b16.in +# +attrs==24.2.0 +certifi==2024.8.30 +coverage[toml]==7.6.1 +elasticsearch==7.13.4 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/.riot/requirements/f20c964.txt b/.riot/requirements/f20c964.txt new file mode 100644 index 00000000000..ab4cf486d17 --- /dev/null +++ b/.riot/requirements/f20c964.txt @@ -0,0 +1,30 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f20c964.in +# +attrs==24.2.0 +blinker==1.8.2 +cachelib==0.9.0 +click==8.1.7 +coverage[toml]==7.6.1 +flask==3.0.3 +flask-caching==2.3.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.1.5 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-memcached==1.62 +redis==5.1.1 +sortedcontainers==2.4.0 +werkzeug==3.0.4 diff --git a/.riot/requirements/f339e99.txt b/.riot/requirements/f339e99.txt new file mode 100644 index 00000000000..b300c0bc5b4 --- /dev/null +++ b/.riot/requirements/f339e99.txt @@ -0,0 +1,19 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f339e99.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/f33b994.txt b/.riot/requirements/f33b994.txt new file mode 100644 index 00000000000..28facac819d --- /dev/null +++ b/.riot/requirements/f33b994.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f33b994.in +# +attrs==24.2.0 +click==8.1.7 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +redis==5.1.1 +rq==1.16.2 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/f46a802.txt b/.riot/requirements/f46a802.txt new file mode 100644 index 00000000000..46033d5a506 --- /dev/null +++ b/.riot/requirements/f46a802.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f46a802.in +# +attrs==24.2.0 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +msgpack==1.1.0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/f4fafb3.txt b/.riot/requirements/f4fafb3.txt new file mode 100644 index 00000000000..09db801e27b --- /dev/null +++ b/.riot/requirements/f4fafb3.txt @@ -0,0 +1,48 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f4fafb3.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==2.1.1 +click==8.1.7 +coverage[toml]==7.6.1 +deprecated==1.2.14 +flask==2.1.3 +gevent==24.2.1 +greenlet==3.1.1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.0.0 +iniconfig==2.0.0 +itsdangerous==2.2.0 +jinja2==3.1.4 +markupsafe==2.0.1 +mock==5.1.0 +opentelemetry-api==1.26.0 +opentelemetry-instrumentation==0.47b0 +opentelemetry-instrumentation-flask==0.47b0 +opentelemetry-instrumentation-wsgi==0.47b0 +opentelemetry-semantic-conventions==0.47b0 +opentelemetry-util-http==0.47b0 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.28.1 +sortedcontainers==2.4.0 +urllib3==1.26.20 +werkzeug==2.1.2 +wrapt==1.16.0 +zipp==3.20.2 +zope-event==5.0 +zope-interface==7.0.3 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.1.0 diff --git a/.riot/requirements/fbee8ab.txt b/.riot/requirements/fbee8ab.txt new file mode 100644 index 00000000000..df12821215c --- /dev/null +++ b/.riot/requirements/fbee8ab.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/fbee8ab.in +# +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +coverage[toml]==7.6.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opensearch-py[requests]==2.0.1 +opentracing==2.4.0 +packaging==24.1 +pluggy==1.5.0 +pytest==8.3.3 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==1.26.20 diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index a6bad81f64c..839f4b3537f 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -1,10 +1,14 @@ from io import BytesIO from io import StringIO import itertools +from typing import TYPE_CHECKING # noqa:F401 from typing import Any -from typing import Sequence from typing import Tuple + +if TYPE_CHECKING: # pragma: no cover + from typing import Sequence # noqa:F401 + from ddtrace.internal._unpatched import _threading as threading from ddtrace.internal.logger import get_logger @@ -263,7 +267,9 @@ def trace_calls_and_returns(frame, event, arg): threading.settrace(trace_calls_and_returns) -def copy_ranges_to_string(pyobject: str, ranges: Sequence[TaintRange]) -> str: +def copy_ranges_to_string(pyobject, ranges): + # type: (str, Sequence[TaintRange]) -> str + # NB this function uses comment-based type annotation because TaintRange is conditionally imported if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] return pyobject @@ -297,7 +303,9 @@ def copy_ranges_to_string(pyobject: str, ranges: Sequence[TaintRange]) -> str: # Given a list of ranges, try to match them with the iterable and return a new iterable with a new range applied that # matched the original one Source. If no range matches, take the Source from the first one. -def copy_ranges_to_iterable_with_strings(iterable: Sequence[str], ranges: Sequence[TaintRange]) -> Sequence[str]: +def copy_ranges_to_iterable_with_strings(iterable, ranges): + # type: (Sequence[str], Sequence[TaintRange]) -> Sequence[str] + # NB this function uses comment-based type annotation because TaintRange is conditionally imported iterable_type = type(iterable) new_result = [] diff --git a/ddtrace/debugging/_expressions.py b/ddtrace/debugging/_expressions.py index ccab7549d8f..32b87017cdf 100644 --- a/ddtrace/debugging/_expressions.py +++ b/ddtrace/debugging/_expressions.py @@ -63,7 +63,9 @@ def _is_identifier(name: str) -> bool: def short_circuit_instrs(op: str, label: Label) -> List[Instr]: value = "FALSE" if op == "and" else "TRUE" - if PY >= (3, 12): + if PY >= (3, 13): + return [Instr("COPY", 1), Instr("TO_BOOL"), Instr(f"POP_JUMP_IF_{value}", label), Instr("POP_TOP")] + elif PY >= (3, 12): return [Instr("COPY", 1), Instr(f"POP_JUMP_IF_{value}", label), Instr("POP_TOP")] return [Instr(f"JUMP_IF_{value}_OR_POP", label)] @@ -145,6 +147,9 @@ def _compile_direct_predicate(self, ast: DDASTType) -> Optional[List[Instr]]: value.append(Instr("LOAD_FAST", "_locals")) value.append(IN_OPERATOR_INSTR) else: + if PY >= (3, 13): + # UNARY_NOT requires a boolean value + value.append(Instr("TO_BOOL")) value.append(Instr("UNARY_NOT")) return value @@ -250,17 +255,18 @@ def _compile_direct_operation(self, ast: DDASTType) -> Optional[List[Instr]]: return None def _call_function(self, func: Callable, *args: List[Instr]) -> List[Instr]: - if PY < (3, 11): - return [Instr("LOAD_CONST", func)] + list(chain(*args)) + [Instr("CALL_FUNCTION", len(args))] - elif PY >= (3, 12): + if PY >= (3, 13): + return [Instr("LOAD_CONST", func), Instr("PUSH_NULL")] + list(chain(*args)) + [Instr("CALL", len(args))] + if PY >= (3, 12): return [Instr("PUSH_NULL"), Instr("LOAD_CONST", func)] + list(chain(*args)) + [Instr("CALL", len(args))] + if PY >= (3, 11): + return ( + [Instr("PUSH_NULL"), Instr("LOAD_CONST", func)] + + list(chain(*args)) + + [Instr("PRECALL", len(args)), Instr("CALL", len(args))] + ) - # Python 3.11 - return ( - [Instr("PUSH_NULL"), Instr("LOAD_CONST", func)] - + list(chain(*args)) - + [Instr("PRECALL", len(args)), Instr("CALL", len(args))] - ) + return [Instr("LOAD_CONST", func)] + list(chain(*args)) + [Instr("CALL_FUNCTION", len(args))] def _compile_arg_operation(self, ast: DDASTType) -> Optional[List[Instr]]: # arg_operation => {"": []} diff --git a/ddtrace/internal/_threads.cpp b/ddtrace/internal/_threads.cpp index 152b7b0da6c..d775544827b 100644 --- a/ddtrace/internal/_threads.cpp +++ b/ddtrace/internal/_threads.cpp @@ -20,8 +20,13 @@ class GILGuard public: inline GILGuard() { - if (!_Py_IsFinalizing()) +#if PY_VERSION_HEX >= 0x030d0000 + if (!Py_IsFinalizing()) { +#else + if (!_Py_IsFinalizing()) { +#endif _state = PyGILState_Ensure(); + } } inline ~GILGuard() { @@ -42,13 +47,23 @@ class AllowThreads public: inline AllowThreads() { - if (!_Py_IsFinalizing()) +#if PY_VERSION_HEX >= 0x30d0000 + if (!Py_IsFinalizing()) { +#else + if (!_Py_IsFinalizing()) { +#endif _state = PyEval_SaveThread(); + } } inline ~AllowThreads() { - if (!_Py_IsFinalizing()) +#if PY_VERSION_HEX >= 0x30d0000 + if (!Py_IsFinalizing()) { +#else + if (!_Py_IsFinalizing()) { +#endif PyEval_RestoreThread(_state); + } } private: @@ -266,8 +281,13 @@ PeriodicThread_start(PeriodicThread* self, PyObject* args) } } - if (_Py_IsFinalizing()) +#if PY_VERSION_HEX >= 0x30d0000 + if (Py_IsFinalizing()) { +#else + if (_Py_IsFinalizing()) { +#endif break; + } if (PeriodicThread__periodic(self)) { // Error @@ -278,8 +298,15 @@ PeriodicThread_start(PeriodicThread* self, PyObject* args) // Run the shutdown callback if there was no error and we are not // at Python shutdown. - if (!self->_atexit && !error && self->_on_shutdown != Py_None && !_Py_IsFinalizing()) - PeriodicThread__on_shutdown(self); + if (!self->_atexit && !error && self->_on_shutdown != Py_None) { +#if PY_VERSION_HEX >= 0x30d0000 + if (!Py_IsFinalizing()) { +#else + if (!_Py_IsFinalizing()) { +#endif + PeriodicThread__on_shutdown(self); + } + } // Notify the join method that the thread has stopped self->_stopped->set(); @@ -418,9 +445,14 @@ PeriodicThread_dealloc(PeriodicThread* self) // Since the native thread holds a strong reference to this object, we // can only get here if the thread has actually stopped. - if (_Py_IsFinalizing()) +#if PY_VERSION_HEX >= 0x30d0000 + if (Py_IsFinalizing()) { +#else + if (_Py_IsFinalizing()) { +#endif // Do nothing. We are about to terminate and release resources anyway. return; + } // If we are trying to stop from the same thread, then we are still running. // This should happen rarely, so we don't worry about the memory leak this diff --git a/ddtrace/internal/injection.py b/ddtrace/internal/injection.py index d6fa2715ec7..787e0160e66 100644 --- a/ddtrace/internal/injection.py +++ b/ddtrace/internal/injection.py @@ -25,8 +25,25 @@ class InvalidLine(Exception): """ +# DEV: This is the bytecode equivalent of +# >>> hook(arg) +# Additionally, we must discard the return value (top of the stack) to restore +# the stack to the state prior to the call. + INJECTION_ASSEMBLY = Assembly() -if PY >= (3, 12): +if PY >= (3, 14): + raise NotImplementedError("Python >= 3.14 is not supported yet") +elif PY >= (3, 13): + INJECTION_ASSEMBLY.parse( + r""" + load_const {hook} + push_null + load_const {arg} + call 1 + pop_top + """ + ) +elif PY >= (3, 12): INJECTION_ASSEMBLY.parse( r""" push_null @@ -91,15 +108,11 @@ def _inject_hook(code: Bytecode, hook: HookType, lineno: int, arg: Any) -> None: if not locs: raise InvalidLine("Line %d does not exist or is either blank or a comment" % lineno) - # DEV: This is the bytecode equivalent of - # >>> hook(arg) - # Additionally, we must discard the return value (top of the stack) to - # restore the stack to the state prior to the call. for i in locs: code[i:i] = INJECTION_ASSEMBLY.bind(dict(hook=hook, arg=arg), lineno=lineno) -_INJECT_HOOK_OPCODE_POS = 0 if PY < (3, 11) else 1 +_INJECT_HOOK_OPCODE_POS = 1 if (3, 11) <= PY < (3, 13) else 0 _INJECT_ARG_OPCODE_POS = 1 if PY < (3, 11) else 2 diff --git a/ddtrace/internal/wrapping/__init__.py b/ddtrace/internal/wrapping/__init__.py index dae0c183ac0..83598e1911c 100644 --- a/ddtrace/internal/wrapping/__init__.py +++ b/ddtrace/internal/wrapping/__init__.py @@ -144,6 +144,8 @@ def wrap_bytecode(wrapper, wrapped): bc.Instr("RESUME", 0, lineno=lineno - 1), bc.Instr("PUSH_NULL", lineno=lineno), ] + if PY >= (3, 13): + instrs[1], instrs[2] = instrs[2], instrs[1] if code.co_cellvars: instrs[0:0] = [Instr("MAKE_CELL", bc.CellVar(_), lineno=lineno) for _ in code.co_cellvars] diff --git a/ddtrace/internal/wrapping/context.py b/ddtrace/internal/wrapping/context.py index 138f542720e..c6b4ee896e2 100644 --- a/ddtrace/internal/wrapping/context.py +++ b/ddtrace/internal/wrapping/context.py @@ -70,7 +70,55 @@ CONTEXT_RETURN = Assembly() CONTEXT_FOOT = Assembly() -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 14): + raise NotImplementedError("Python >= 3.14 is not supported yet") +elif sys.version_info >= (3, 13): + CONTEXT_HEAD.parse( + r""" + load_const {context_enter} + push_null + call 0 + pop_top + """ + ) + CONTEXT_RETURN.parse( + r""" + push_null + load_const {context_return} + swap 3 + call 1 + """ + ) + + CONTEXT_RETURN_CONST = Assembly() + CONTEXT_RETURN_CONST.parse( + r""" + load_const {context_return} + push_null + load_const {value} + call 1 + """ + ) + + CONTEXT_FOOT.parse( + r""" + try @_except lasti + push_exc_info + load_const {context_exit} + push_null + call 0 + pop_top + reraise 2 + tried + + _except: + copy 3 + pop_except + reraise 1 + """ + ) + +elif sys.version_info >= (3, 12): CONTEXT_HEAD.parse( r""" push_null diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index f3758d13989..c7ba1ec3e83 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -157,7 +157,11 @@ from cpython.ref cimport Py_DECREF cdef extern from "": PyObject* _PyThread_CurrentFrames() -IF PY_VERSION_HEX >= 0x030b0000: +IF PY_VERSION_HEX >= 0x30d0000: + cdef extern from "": + PyObject* _PyThread_CurrentExceptions() + +ELIF PY_VERSION_HEX >= 0x030b0000: cdef extern from "": PyObject* _PyThread_CurrentExceptions() diff --git a/docker-compose.yml b/docker-compose.yml index cf3738c2dbe..cf40a4a256d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,6 +152,10 @@ services: - "127.0.0.1:5433:5433" testrunner: + # DEV uncomment to test local changes to the Dockerfile + # build: + # context: ./docker + # dockerfile: Dockerfile image: ghcr.io/datadog/dd-trace-py/testrunner:47c7b5287da25643e46652e6d222a40a52f2382a@sha256:3a02dafeff9cd72966978816d1b39b54f5517af4049396923b95c8452f604269 command: bash environment: diff --git a/docker/.python-version b/docker/.python-version index decc1955c11..9924540f9a4 100644 --- a/docker/.python-version +++ b/docker/.python-version @@ -4,4 +4,4 @@ 3.9 3.10 3.11 -3.13-dev +3.13 diff --git a/docker/Dockerfile b/docker/Dockerfile index 79f207724db..8ff9be89e48 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # DEV: Use `debian:slim` instead of an `alpine` image to support installing wheels from PyPI # this drastically improves test execution time since python dependencies don't all # have to be built from source all the time (grpcio takes forever to install) -FROM debian:buster-20221219-slim +FROM debian:bookworm-slim ARG TARGETARCH ARG HATCH_VERSION=1.12.0 @@ -34,7 +34,6 @@ RUN apt-get update \ gnupg \ jq \ libbz2-dev \ - libenchant-dev \ libffi-dev \ liblzma-dev \ libmemcached-dev \ @@ -47,9 +46,7 @@ RUN apt-get update \ libsqlite3-dev \ libsqliteodbc \ libssh-dev \ - libssl-dev \ patch \ - python-openssl\ unixodbc-dev \ wget \ zlib1g-dev \ @@ -61,7 +58,7 @@ RUN apt-get install -y --no-install-recommends nodejs npm \ # MariaDB is a dependency for tests RUN curl https://mariadb.org/mariadb_release_signing_key.pgp | gpg --dearmor > /etc/apt/trusted.gpg.d/mariadb.gpg \ - && echo "deb [arch=amd64,arm64] https://mirror.mariadb.org/repo/11.rolling/debian/ buster main" > /etc/apt/sources.list.d/mariadb.list \ + && echo "deb [arch=amd64,arm64] https://mirror.mariadb.org/repo/11.rolling/debian/ bookworm main" > /etc/apt/sources.list.d/mariadb.list \ && apt-get update \ && apt-get install -y --no-install-recommends libmariadb-dev libmariadb-dev-compat @@ -71,7 +68,7 @@ RUN if [ "$TARGETARCH" = "amd64" ]; \ then \ curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \ - && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-buster-prod buster main" > /etc/apt/sources.list.d/dotnetdev.list \ + && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-bookworm-prod bookworm main" > /etc/apt/sources.list.d/dotnetdev.list \ && apt-get update \ && apt-get install -y --no-install-recommends azure-functions-core-tools-4=4.0.6280-1; \ fi @@ -93,7 +90,7 @@ RUN curl https://sh.rustup.rs -sSf | \ sh -s -- --default-toolchain stable -y # Install pyenv and necessary Python versions -RUN git clone --depth 1 --branch v2.4.2 https://github.com/pyenv/pyenv "${PYENV_ROOT}" \ +RUN git clone --depth 1 --branch v2.4.22 https://github.com/pyenv/pyenv "${PYENV_ROOT}" \ && cd /root \ && pyenv local | xargs -L 1 pyenv install \ && cd - diff --git a/docs/versioning.rst b/docs/versioning.rst index 0972213f51c..fdd71f8de08 100644 --- a/docs/versioning.rst +++ b/docs/versioning.rst @@ -109,17 +109,17 @@ Supported runtimes * - Linux - x86-64, i686, AArch64 - CPython - - 3.7-3.12 + - 3.7-3.13 - ``>=2.0,<3`` * - MacOS - Intel, Apple Silicon - CPython - - 3.7-3.12 + - 3.7-3.13 - ``>=2.0,<3`` * - Windows - 64bit, 32bit - CPython - - 3.7-3.12 + - 3.7-3.13 - ``>=2.0,<3`` * - Linux - x86-64, i686, AArch64 diff --git a/hatch.toml b/hatch.toml index f3a3c2cee36..7dae1538613 100644 --- a/hatch.toml +++ b/hatch.toml @@ -172,7 +172,7 @@ extra-dependencies = [ ] [[envs.integration_test.matrix]] -python = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] [envs.integration_test.env-vars] _DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDER = "1" @@ -294,7 +294,7 @@ test = [ ] [[envs.appsec_iast_native.matrix]] -python = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] +python = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] ## ASM FastAPI @@ -364,7 +364,7 @@ test = [ ] [[envs.appsec_aggregated_leak_testing.matrix]] -python = ["3.10", "3.11", "3.12"] +python = ["3.10", "3.11", "3.12", "3.13"] @@ -468,7 +468,7 @@ pytest = ["~=6.0", "~=7.0"] [[envs.pytest_plugin_v2.matrix]] -python = ["3.9", "3.10", "3.12"] +python = ["3.9", "3.10", "3.12", "3.13"] pytest = ["~=6.0", "~=7.0", "~=8.0"] [envs.snapshot_viewer] diff --git a/lib-injection/dl_wheels.py b/lib-injection/dl_wheels.py index e10d8e53e0e..81c5715611d 100755 --- a/lib-injection/dl_wheels.py +++ b/lib-injection/dl_wheels.py @@ -16,6 +16,7 @@ ./dl_wheels.py --help """ + import argparse import itertools import os @@ -41,9 +42,9 @@ ) # Supported Python versions lists all python versions that can install at least one version of the ddtrace library. -supported_versions = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] +supported_versions = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] supported_arches = ["aarch64", "x86_64", "i686"] -supported_platforms = ["musllinux_1_1", "manylinux2014"] +supported_platforms = ["musllinux_1_2", "manylinux2014"] parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py index 7d28a3c4d4a..0f87b770edd 100644 --- a/lib-injection/sources/sitecustomize.py +++ b/lib-injection/sources/sitecustomize.py @@ -264,7 +264,7 @@ def _inject(): except Exception: _log("user-installed ddtrace not found, configuring application to use injection site-packages") - current_platform = "manylinux2014" if _get_clib() == "gnu" else "musllinux_1_1" + current_platform = "manylinux2014" if _get_clib() == "gnu" else "musllinux_1_2" _log("detected platform %s" % current_platform, level="debug") pkgs_path = os.path.join(SCRIPT_DIR, "ddtrace_pkgs") diff --git a/pyproject.toml b/pyproject.toml index 9c8ff26d223..3c83cfc5067 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,13 +23,16 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ - "bytecode>=0.15.0; python_version>='3.12'", + "bytecode>=0.16.0; python_version>='3.13.0'", + "bytecode>=0.15.0; python_version~='3.12.0'", "bytecode>=0.14.0; python_version~='3.11.0'", "bytecode>=0.13.0; python_version<'3.11'", "envier~=0.5", "importlib_metadata<=6.5.0; python_version<'3.8'", + "legacy-cgi>=2.0.0; python_version>='3.13.0'", "opentelemetry-api>=1", "protobuf>=3", "typing_extensions", diff --git a/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml b/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml new file mode 100644 index 00000000000..837858691fe --- /dev/null +++ b/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Makes the library compatible with Python 3.13 diff --git a/riotfile.py b/riotfile.py index 86d6ed5bf76..6db9102786f 100644 --- a/riotfile.py +++ b/riotfile.py @@ -17,7 +17,8 @@ (3, 10), (3, 11), (3, 12), -] + (3, 13), +] # type: List[Tuple[int, int]] def version_to_str(version: Tuple[int, int]) -> str: @@ -70,9 +71,9 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT """Helper to select python versions from the list of versions we support >>> select_pys() - ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] >>> select_pys(min_version='3') - ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] >>> select_pys(max_version='3') [] >>> select_pys(min_version='3.7', max_version='3.9') @@ -142,7 +143,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="appsec_iast", - pys=select_pys(), + pys=select_pys(max_version="3.12"), command="pytest -v {cmdargs} tests/appsec/iast/", pkgs={ "requests": latest, @@ -164,7 +165,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="appsec_iast_memcheck", - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), command="pytest {cmdargs} --memray --stacks=35 tests/appsec/iast_memcheck/", pkgs={ "requests": latest, @@ -263,7 +264,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), # Flask 3.x.x Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(min_version="3.8", max_version="3.12"), pkgs={ "flask": "~=3.0", "langchain": "==0.0.354", @@ -396,7 +397,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "msgpack": [latest], "pytest-randomly": latest, }, - pys=select_pys(), + pys=select_pys(max_version="3.12"), venvs=[ Venv( name="datastreams-latest", @@ -520,7 +521,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} --no-cov tests/commands/test_runner.py", venvs=[ Venv( - pys=select_pys(), + pys=select_pys(max_version="3.12"), pkgs={ "redis": latest, "gevent": latest, @@ -606,7 +607,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="falcon", command="pytest {cmdargs} tests/contrib/falcon", - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ "falcon": [ "~=3.0.0", @@ -828,7 +829,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # django started supporting psycopg3 in 4.2 for versions >3.1.8 - pys=select_pys(min_version="3.8"), + pys=select_pys(min_version="3.8", max_version="3.12"), pkgs={ "django": ["~=4.2"], "psycopg": latest, @@ -919,7 +920,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.12"), + pys="3.12", pkgs={ "sqlalchemy": latest, "django": latest, @@ -1208,7 +1209,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"psycopg2-binary": "~=2.8.0"}, ), Venv( - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), # psycopg2-binary added support for Python 3.9/3.10 in 2.9.1 # psycopg2-binary added support for Python 3.11 in 2.9.2 pkgs={"psycopg2-binary": ["~=2.9.2", latest]}, @@ -1232,7 +1233,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.12"), + pys=select_pys(min_version="3.12", max_version="3.12"), pkgs={ "pytest-asyncio": "==0.23.7", }, @@ -1312,7 +1313,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"starlette": ["~=0.21.0", "~=0.33.0", latest]}, ), Venv( - pys=select_pys(min_version="3.12"), + pys="3.12", pkgs={"starlette": latest}, ), ], @@ -1335,7 +1336,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ "sqlalchemy": ["~=1.3.0", latest], "psycopg2-binary": latest, @@ -1436,7 +1437,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(min_version="3.8", max_version="3.12"), pkgs={"botocore": "==1.34.49", "boto3": "==1.34.49"}, ), ], @@ -1505,7 +1506,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"pymysql": "~=0.10"}, ), Venv( - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ "pymysql": [ "~=1.0", @@ -1561,7 +1562,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.12"), + pys=select_pys(min_version="3.12", max_version="3.12"), pkgs={"aiobotocore": latest}, ), ], @@ -1584,14 +1585,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # fastapi added support for Python 3.11 in 0.86.0 - pys=select_pys(min_version="3.11"), + pys=select_pys(min_version="3.11", max_version="3.12"), pkgs={"fastapi": ["~=0.86.0", latest], "anyio": ">=3.4.0,<4.0"}, ), ], ), Venv( name="aiomysql", - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), command="pytest {cmdargs} tests/contrib/aiomysql", pkgs={ "pytest-randomly": latest, @@ -1638,7 +1639,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ], ), Venv( - pys=select_pys(min_version="3.10"), + pys=select_pys(min_version="3.10", max_version="3.12"), pkgs={ "pytest": [ "~=6.0", @@ -1719,7 +1720,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ], ), Venv( - pys=select_pys(min_version="3.10"), + pys=select_pys(min_version="3.10", max_version="3.12"), pkgs={ "pytest-bdd": [ ">=4.0,<5.0", @@ -1800,7 +1801,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # grpcio added support for Python 3.12 in 1.59 - pys=select_pys(min_version="3.12"), + pys=select_pys(min_version="3.12", max_version="3.12"), pkgs={ "grpcio": ["~=1.59.0", latest], "pytest-asyncio": "==0.23.7", @@ -2002,7 +2003,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.7"), + pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ "aiopg": ["~=1.0", "~=1.4.0"], }, @@ -2247,7 +2248,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.12"), + pys="3.12", pkgs={ "sanic": [latest], "sanic-testing": "~=22.3.0", @@ -2322,7 +2323,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"asyncpg": ["~=0.27", latest]}, ), Venv( - pys=select_pys(min_version="3.12"), + pys=select_pys(min_version="3.12", max_version="3.12"), pkgs={"asyncpg": [latest]}, ), ], @@ -2355,7 +2356,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # sqlite3 is tied to the Python version and is not installable via pip # To test a range of versions without updating Python, we use Linux only pysqlite3-binary package # Remove pysqlite3-binary on Python 3.9+ locally on non-linux machines - Venv(pys=select_pys(min_version="3.9"), pkgs={"pysqlite3-binary": [latest]}), + Venv(pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={"pysqlite3-binary": [latest]}), Venv(pys=select_pys(max_version="3.8"), pkgs={"importlib-metadata": latest}), ], ), @@ -2420,7 +2421,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="consul", - pys=select_pys(), + pys=select_pys(max_version="3.12"), command="pytest {cmdargs} tests/contrib/consul", pkgs={ "python-consul": [ @@ -2534,8 +2535,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"gevent": latest}, ), Venv( - pys=select_pys(min_version="3.12"), - pkgs={"gevent": latest}, + pys="3.12", + pkgs={"gevent": "~=23.9.0"}, ), ], ), @@ -2557,7 +2558,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # pyodbc added support for Python 3.11 in 4.0.35 - pys=select_pys(min_version="3.11"), + pys=select_pys(min_version="3.11", max_version="3.12"), pkgs={"pyodbc": [latest]}, ), ], @@ -2624,7 +2625,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # tornado added support for Python 3.10 in 6.2 - pys=select_pys(min_version="3.10"), + pys=select_pys(min_version="3.10", max_version="3.12"), pkgs={"tornado": ["==6.2", "==6.3.1"]}, ), ], @@ -2640,7 +2641,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # mysqlclient added support for Python 3.9/3.10 in 2.1 - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={"mysqlclient": ["~=2.1", latest]}, ), ], @@ -2709,7 +2710,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "cohere": latest, "anthropic": "==0.26.0", }, - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), ), Venv( pkgs={ @@ -2727,14 +2728,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "botocore": latest, "cohere": latest, }, - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), ), ], ), Venv( name="anthropic", command="pytest {cmdargs} tests/contrib/anthropic", - pys=select_pys(min_version="3.8"), + pys=select_pys(min_version="3.8", max_version="3.12"), pkgs={ "pytest-asyncio": latest, "vcrpy": latest, @@ -2744,7 +2745,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="google_generativeai", command="pytest {cmdargs} tests/contrib/google_generativeai", - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pytest-asyncio": latest, "google-generativeai": [latest], @@ -2756,7 +2757,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="vertexai", command="pytest {cmdargs} tests/contrib/vertexai", - pys=select_pys(min_version="3.9"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pytest-asyncio": latest, "vertexai": [latest], @@ -2820,7 +2821,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"confluent-kafka": ["~=1.9.2", latest]}, ), # confluent-kafka added support for Python 3.11 in 2.0.2 - Venv(pys=select_pys(min_version="3.11"), pkgs={"confluent-kafka": latest}), + Venv(pys=select_pys(min_version="3.11", max_version="3.12"), pkgs={"confluent-kafka": latest}), ], ), ], @@ -2858,7 +2859,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="ci_visibility", command="pytest --no-ddtrace {cmdargs} tests/ci_visibility", - pys=select_pys(), + pys=select_pys(max_version="3.12"), pkgs={ "msgpack": latest, "coverage": latest, @@ -3003,7 +3004,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), # Python 3.12 Venv( - pys=select_pys(min_version="3.12"), + pys="3.12", pkgs={"uwsgi": latest}, venvs=[ Venv( @@ -3143,7 +3144,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), # Python 3.12 Venv( - pys=select_pys(min_version="3.12"), + pys="3.12", pkgs={"uwsgi": latest}, venvs=[ Venv( diff --git a/setup.py b/setup.py index 13b0cb4a4f0..74e8f8187d7 100644 --- a/setup.py +++ b/setup.py @@ -527,7 +527,7 @@ def get_exts_for(name): sources=[ "ddtrace/appsec/_iast/_stacktrace.c", ], - extra_compile_args=debug_compile_args, + extra_compile_args=extra_compile_args + debug_compile_args, ) ) @@ -553,7 +553,7 @@ def get_exts_for(name): ) # Echion doesn't build on 3.7, so just skip it outright for now - if sys.version_info >= (3, 8): + if sys.version_info >= (3, 8) and sys.version_info < (3, 13): ext_modules.append( CMakeExtension( "ddtrace.internal.datadog.profiling.stack_v2._stack_v2", diff --git a/src/core/Cargo.lock b/src/core/Cargo.lock index 27f510e5ddc..f840798f96e 100644 --- a/src/core/Cargo.lock +++ b/src/core/Cargo.lock @@ -14,12 +14,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - [[package]] name = "bytes" version = "1.6.1" @@ -46,7 +40,7 @@ version = "0.1.0" dependencies = [ "datadog-ddsketch", "pyo3", - "pyo3-build-config", + "pyo3-build-config 0.21.2", ] [[package]] @@ -57,9 +51,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" @@ -82,16 +76,6 @@ version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -107,29 +91,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "parking_lot" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "portable-atomic" version = "1.6.0" @@ -170,17 +131,17 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", "portable-atomic", - "pyo3-build-config", + "pyo3-build-config 0.22.3", "pyo3-ffi", "pyo3-macros", "unindent", @@ -196,21 +157,31 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "pyo3-build-config" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" +dependencies = [ + "once_cell", + "target-lexicon", +] + [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" dependencies = [ "libc", - "pyo3-build-config", + "pyo3-build-config 0.22.3", ] [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -220,13 +191,13 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", + "pyo3-build-config 0.22.3", "quote", "syn 2.0.61", ] @@ -240,27 +211,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - [[package]] name = "syn" version = "1.0.109" @@ -300,67 +250,3 @@ name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 3353bc8b504..94eeb6d7a3e 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -9,7 +9,7 @@ strip = "debuginfo" opt-level = 3 [dependencies] -pyo3 = { version = "0.21.2", features = ["extension-module"] } +pyo3 = { version = "0.22.3", features = ["extension-module"] } datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v14.3.1" } [build-dependencies] diff --git a/tests/contrib/futures/test_propagation.py b/tests/contrib/futures/test_propagation.py index d4d5beb8946..037aebd9ce5 100644 --- a/tests/contrib/futures/test_propagation.py +++ b/tests/contrib/futures/test_propagation.py @@ -1,4 +1,5 @@ import concurrent.futures +import sys import time import pytest @@ -406,6 +407,7 @@ def fn(): assert spans[1].parent_id == spans[0].span_id +@pytest.mark.skipif(sys.version_info > (3, 12), reason="Fails on 3.13") @pytest.mark.subprocess(ddtrace_run=True, timeout=5) def test_concurrent_futures_with_gevent(): """Check compatibility between the integration and gevent""" diff --git a/tests/internal/crashtracker/test_crashtracker.py b/tests/internal/crashtracker/test_crashtracker.py index ed338ce95bb..a4074745f83 100644 --- a/tests/internal/crashtracker/test_crashtracker.py +++ b/tests/internal/crashtracker/test_crashtracker.py @@ -506,6 +506,7 @@ def test_crashtracker_user_tags_envvar(run_python_code_in_subprocess): @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +@pytest.mark.skipif(sys.version_info > (3, 12), reason="Fails on 3.13") def test_crashtracker_set_tag_profiler_config(run_python_code_in_subprocess): port, sock = utils.crashtracker_receiver_bind() assert sock diff --git a/tests/internal/symbol_db/test_symbols.py b/tests/internal/symbol_db/test_symbols.py index a97f6c5bcee..56fa45b3edc 100644 --- a/tests/internal/symbol_db/test_symbols.py +++ b/tests/internal/symbol_db/test_symbols.py @@ -1,5 +1,6 @@ from importlib.machinery import ModuleSpec from pathlib import Path +import sys from types import ModuleType import typing as t @@ -22,6 +23,7 @@ def foo(a, b, c=None): assert {s.name for s in symbols if s.symbol_type == SymbolType.LOCAL} == {"loc"} +@pytest.mark.skipif(sys.version_info > (3, 12), reason="fails on 3.13") def test_symbols_class(): class Sup: pass diff --git a/tests/internal/test_forksafe.py b/tests/internal/test_forksafe.py index e9c5a42c9ef..f9a32f460c5 100644 --- a/tests/internal/test_forksafe.py +++ b/tests/internal/test_forksafe.py @@ -1,5 +1,6 @@ from collections import Counter import os +import sys import pytest @@ -299,6 +300,7 @@ def fn(): assert exit_code == 42 +@pytest.mark.skipif(sys.version_info > (3, 12), reason="fails on 3.13") @pytest.mark.subprocess( out=lambda _: Counter(_) == {"C": 3, "T": 4}, err=None, diff --git a/tests/internal/test_injection.py b/tests/internal/test_injection.py index 3b74c589d62..871726620a8 100644 --- a/tests/internal/test_injection.py +++ b/tests/internal/test_injection.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +import sys import mock import pytest @@ -205,6 +206,7 @@ def test_inject_in_loop(): assert hook.call_count == n +@pytest.mark.skipif(sys.version_info > (3, 12), reason="Fails on 3.13") def test_inject_in_generator(): lo = next(iter(linenos(generator_target))) hook = mock.Mock() diff --git a/tests/internal/test_wrapping.py b/tests/internal/test_wrapping.py index d27eadac43b..7c9a071545d 100644 --- a/tests/internal/test_wrapping.py +++ b/tests/internal/test_wrapping.py @@ -95,6 +95,7 @@ def f(a, b, c=None): assert not channel1 and not channel2 +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") def test_wrap_generator(): channel = [] @@ -116,6 +117,7 @@ def g(): assert list(g()) == list(range(10)) == channel +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") def test_wrap_generator_send(): def wrapper(f, args, kwargs): return f(*args, **kwargs) @@ -142,6 +144,7 @@ def g(): assert list(range(10)) == channel +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") def test_wrap_generator_throw_close(): def wrapper_maker(channel): def wrapper(f, args, kwargs): @@ -215,6 +218,7 @@ def f(): assert [frame.f_code.co_name for frame in f()[:4]] == ["f", "wrapper", "f", "test_wrap_stack"] +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") @pytest.mark.asyncio async def test_wrap_async_context_manager_exception_on_exit(): def wrapper(f, args, kwargs): @@ -231,6 +235,7 @@ async def g(): await acm.__aexit__(ValueError, None, None) +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") def test_wrap_generator_yield_from(): channel = [] @@ -304,6 +309,7 @@ def wrapper(f, args, kwargs): assert f(1, path="bar", foo="baz") == (1, (), "bar", {"foo": "baz"}) +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") @pytest.mark.asyncio async def test_async_generator(): async def stream(): @@ -340,6 +346,7 @@ async def agwrapper(f, args, kwargs): assert awrapper_called +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") @pytest.mark.asyncio async def test_wrap_async_generator_send(): def wrapper(f, args, kwargs): @@ -372,6 +379,7 @@ async def consume(): await consume() +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") @pytest.mark.asyncio async def test_double_async_for_with_exception(): channel = None @@ -416,6 +424,7 @@ async def stream(): b"".join([_ async for _ in s]) +@pytest.mark.skipif(sys.version_info > (3, 12), reason="segfault on 3.13") @pytest.mark.asyncio async def test_wrap_async_generator_throw_close(): channel = [] From 1501bff342c55fca79d2687e75f1f1b3382d2fb9 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:10:38 -0500 Subject: [PATCH 331/372] chore(profiling): refactor fetch libdatadog (#11747) This patch converts fetch libdatadog to a pure cmake implementation and updates the way the `dd_wrapper` is called so it should only be built once. In current-main with an empty `build` directory: ``` Performance counter stats for './build_standalone.sh -- -- --': 63,864.14 msec task-clock:u # 1.005 CPUs utilized 0 context-switches:u # 0.000 /sec 0 cpu-migrations:u # 0.000 /sec 2,853,585 page-faults:u # 44.682 K/sec cycles:u instructions:u branches:u branch-misses:u 63.534837508 seconds time elapsed 53.238101000 seconds user 10.718485000 seconds sys ``` After applying this patch: ``` Performance counter stats for './build_standalone.sh -- -- --': 33,262.53 msec task-clock:u # 0.985 CPUs utilized 0 context-switches:u # 0.000 /sec 0 cpu-migrations:u # 0.000 /sec 1,638,895 page-faults:u # 49.272 K/sec cycles:u instructions:u branches:u branch-misses:u 33.757125924 seconds time elapsed 27.434846000 seconds user 5.887714000 seconds sys ``` So--a small difference, but a good difference. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim --- .../datadog/profiling/build_standalone.sh | 8 +- .../profiling/cmake/FindLibdatadog.cmake | 103 ++++++++++++++++-- .../profiling/cmake/tools/fetch_libdatadog.sh | 100 ----------------- .../cmake/tools/libdatadog_checksums.txt | 5 - .../profiling/crashtracker/CMakeLists.txt | 11 +- .../profiling/dd_wrapper/CMakeLists.txt | 17 ++- .../datadog/profiling/ddup/CMakeLists.txt | 13 +-- .../datadog/profiling/stack_v2/CMakeLists.txt | 11 +- 8 files changed, 125 insertions(+), 143 deletions(-) delete mode 100755 ddtrace/internal/datadog/profiling/cmake/tools/fetch_libdatadog.sh delete mode 100644 ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt diff --git a/ddtrace/internal/datadog/profiling/build_standalone.sh b/ddtrace/internal/datadog/profiling/build_standalone.sh index 286f6d179a2..beeda4f21b4 100755 --- a/ddtrace/internal/datadog/profiling/build_standalone.sh +++ b/ddtrace/internal/datadog/profiling/build_standalone.sh @@ -103,8 +103,8 @@ cmake_args=( -DPython3_ROOT_DIR=$(python3 -c "import sysconfig; print(sysconfig.get_config_var('prefix'))") ) -# Initial build targets; no matter what, dd_wrapper is the base dependency, so it's always built -targets=("dd_wrapper") +# Initial build targets; start out empty +targets=() set_cc() { if [ -z "${CC:-}" ]; then @@ -333,7 +333,9 @@ add_target() { targets+=("crashtracker") ;; dd_wrapper) - # We always build dd_wrapper, so no need to add it to the list + # `dd_wrapper` is a dependency of other targets, but the overall structure is weird when it's given explicitly + # so we only include it when it's called explicitly + targets+=("dd_wrapper") ;; stack_v2) targets+=("stack_v2") diff --git a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake index 6e103fe7d70..3a96fbeb353 100644 --- a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake @@ -1,27 +1,106 @@ -# Only add this project if Datadog::Profiling is not already defined +# Only proceed if Datadog::Profiling (provided by libdatadog) isn't already defined if(TARGET Datadog::Profiling) return() endif() -include(ExternalProject) -set(TAG_LIBDATADOG - "v14.3.1" - CACHE STRING "libdatadog github tag") +# Set the FetchContent paths early +set(FETCHCONTENT_BASE_DIR + "${CMAKE_CURRENT_BINARY_DIR}/_deps" + CACHE PATH "FetchContent base directory") +set(FETCHCONTENT_DOWNLOADS_DIR + "${FETCHCONTENT_BASE_DIR}/downloads" + CACHE PATH "FetchContent downloads directory") -set(Datadog_BUILD_DIR ${CMAKE_BINARY_DIR}/libdatadog) -set(Datadog_ROOT ${Datadog_BUILD_DIR}/libdatadog-${TAG_LIBDATADOG}) +include_guard(GLOBAL) +include(FetchContent) -message(STATUS "${CMAKE_CURRENT_LIST_DIR}/tools/fetch_libdatadog.sh ${TAG_LIBDATADOG} ${Datadog_ROOT}") -execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/tools/fetch_libdatadog.sh" ${TAG_LIBDATADOG} ${Datadog_ROOT} - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} COMMAND_ERROR_IS_FATAL ANY) +# Set version if not already set +if(NOT DEFINED TAG_LIBDATADOG) + set(TAG_LIBDATADOG + "v14.3.1" + CACHE STRING "libdatadog github tag") +endif() + +if(NOT DEFINED DD_CHECKSUMS) + set(DD_CHECKSUMS + "57f83aff275628bb1af89c22bb4bd696726daf2a9e09b6cd0d966b29e65a7ad6 libdatadog-aarch64-alpine-linux-musl.tar.gz" + "2be2efa98dfc32f109abdd79242a8e046a7a300c77634135eb293e000ecd4a4c libdatadog-aarch64-apple-darwin.tar.gz" + "36db8d50ccabb71571158ea13835c0f1d05d30b32135385f97c16343cfb6ddd4 libdatadog-aarch64-unknown-linux-gnu.tar.gz" + "2f61fd21cf2f8147743e414b4a8c77250a17be3aecc42a69ffe54f0a603d5c92 libdatadog-x86_64-alpine-linux-musl.tar.gz" + "f01f05600591063eba4faf388f54c155ab4e6302e5776c7855e3734955f7daf7 libdatadog-x86_64-unknown-linux-gnu.tar.gz") +endif() + +# Determine platform-specific tarball name in a way that conforms to the libdatadog naming scheme in Github releases +if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") + set(DD_ARCH "aarch64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64") + set(DD_ARCH "x86_64") +else() + message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}") +endif() + +if(APPLE) + set(DD_PLATFORM "apple-darwin") +elseif(UNIX) + execute_process( + COMMAND ldd --version + OUTPUT_VARIABLE LDD_OUTPUT + ERROR_VARIABLE LDD_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(LDD_OUTPUT MATCHES "musl") + set(DD_PLATFORM "alpine-linux-musl") + else() + set(DD_PLATFORM "unknown-linux-gnu") + endif() +else() + message(FATAL_ERROR "Unsupported operating system") +endif() + +set(DD_TARBALL "libdatadog-${DD_ARCH}-${DD_PLATFORM}.tar.gz") + +# Make sure we can get the checksum for the tarball +foreach(ENTRY IN LISTS DD_CHECKSUMS) + if(ENTRY MATCHES "^([a-fA-F0-9]+) ${DD_TARBALL}$") + set(DD_HASH "${CMAKE_MATCH_1}") + break() + endif() +endforeach() + +if(NOT DEFINED DD_HASH) + message(FATAL_ERROR "Could not find checksum for ${DD_TARBALL}") +endif() + +# Clean up any existing downloads if they exist +set(TARBALL_PATH "${FETCHCONTENT_DOWNLOADS_DIR}/${DD_TARBALL}") +if(EXISTS "${TARBALL_PATH}") + file(SHA256 "${TARBALL_PATH}" EXISTING_HASH) + if(NOT EXISTING_HASH STREQUAL DD_HASH) + file(REMOVE "${TARBALL_PATH}") + # Also remove the subbuild directory to force a fresh download + file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/_deps/libdatadog-subbuild") + endif() +endif() + +# Use FetchContent to download and extract the library +FetchContent_Declare( + libdatadog + URL "https://github.com/DataDog/libdatadog/releases/download/${TAG_LIBDATADOG}/${DD_TARBALL}" + URL_HASH SHA256=${DD_HASH} + DOWNLOAD_DIR "${FETCHCONTENT_DOWNLOADS_DIR}" SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/libdatadog-src") + +# Make the content available +FetchContent_MakeAvailable(libdatadog) +# Set up paths +get_filename_component(Datadog_ROOT "${libdatadog_SOURCE_DIR}" ABSOLUTE) set(Datadog_DIR "${Datadog_ROOT}/cmake") -# Prefer static library to shared library +# Configure library preferences (static over shared) set(CMAKE_FIND_LIBRARY_SUFFIXES_BACKUP ${CMAKE_FIND_LIBRARY_SUFFIXES}) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) +# Find the package find_package(Datadog REQUIRED) -# Restore CMAKE_FIND_LIBRARY_SUFFIXES +# Restore library preferences set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BACKUP}) diff --git a/ddtrace/internal/datadog/profiling/cmake/tools/fetch_libdatadog.sh b/ddtrace/internal/datadog/profiling/cmake/tools/fetch_libdatadog.sh deleted file mode 100755 index a1e55066089..00000000000 --- a/ddtrace/internal/datadog/profiling/cmake/tools/fetch_libdatadog.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash -# http://redsymbol.net/articles/unofficial-bash-strict-mode/ -set -euox pipefail -IFS=$'\n\t' - -usage() { - echo "Usage :" - echo "$0 " - echo "" - echo "Example" - echo " $0 v0.7.0-rc.1 ./vendor" -} - -if [ $# != 2 ] || [ "$1" == "-h" ]; then - usage - exit 1 -fi - -SCRIPTPATH=$(readlink -f "$0") -SCRIPTDIR=$(dirname "$SCRIPTPATH") - -OS_NAME=$(uname -s) -MARCH=$(uname -m) - -TAG_LIBDATADOG=$1 -TARGET_EXTRACT=$2 - -CHECKSUM_FILE=${SCRIPTDIR}/libdatadog_checksums.txt - -# if os is darwin, set distribution to apple-darwin and march to aarch64 -if [[ "$OS_NAME" == "Darwin" ]]; then - DISTRIBUTION="apple-darwin" - # if march is arm64 set it to aarch64 - if [[ "$MARCH" == "arm64" ]]; then - MARCH="aarch64" - else - echo "Unsupported architecture $MARCH for $OS_NAME" - exit 1 - fi -elif [[ "$OS_NAME" == "Linux" ]]; then - # Test for musl - MUSL_LIBC=$(ldd /bin/ls | grep 'musl' | head -1 | cut -d ' ' -f1 || true) - if [[ -n ${MUSL_LIBC-""} ]]; then - DISTRIBUTION="alpine-linux-musl" - else - DISTRIBUTION="unknown-linux-gnu" - fi -else - echo "Unsupported OS $OS_NAME" - exit 1 -fi - -# https://github.com/DataDog/libdatadog/releases/download/v0.7.0-rc.1/libdatadog-aarch64-alpine-linux-musl.tar.gz -TAR_LIBDATADOG=libdatadog-${MARCH}-${DISTRIBUTION}.tar.gz -GITHUB_URL_LIBDATADOG=https://github.com/DataDog/libdatadog/releases/download/${TAG_LIBDATADOG}/${TAR_LIBDATADOG} - -SHA256_LIBDATADOG="blank" -while IFS=' ' read -r checksum filename; do - if [ "$filename" == "$TAR_LIBDATADOG" ]; then - SHA256_LIBDATADOG="$checksum $filename" - break - fi -done < "$CHECKSUM_FILE" - -if [ "$SHA256_LIBDATADOG" == "blank" ]; then - echo "Could not find checksum for ${TAR_LIBDATADOG} in ${CHECKSUM_FILE}" - exit 1 -else - echo "Using libdatadog sha256: ${SHA256_LIBDATADOG}" -fi - -mkdir -p "$TARGET_EXTRACT" || true -cd "$TARGET_EXTRACT" - -if [[ -e "${TAR_LIBDATADOG}" ]]; then - already_present=1 -else - already_present=0 - echo "Downloading libdatadog ${GITHUB_URL_LIBDATADOG}..." - if command -v curl > /dev/null 2>&1; then - curl -fsSLO "${GITHUB_URL_LIBDATADOG}" - elif command -v wget > /dev/null 2>&1; then - wget -q -O "${GITHUB_URL_LIBDATADOG##*/}" "${GITHUB_URL_LIBDATADOG}" - else - echo "Error: neither curl nor wget is available." >&2 - exit 1 - fi -fi - -echo "Checking libdatadog sha256" -if ! echo "${SHA256_LIBDATADOG}" | sha256sum -c -; then - echo "Error validating libdatadog SHA256" - echo "Please clear $TARGET_EXTRACT before restarting" - exit 1 -fi - -if [[ $already_present -eq 0 || ! -f "cmake/DatadogConfig.cmake" ]]; then - echo "Extracting ${TAR_LIBDATADOG}" - tar xf "${TAR_LIBDATADOG}" --strip-components=1 --no-same-owner -fi diff --git a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt b/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt deleted file mode 100644 index ca856e996ae..00000000000 --- a/ddtrace/internal/datadog/profiling/cmake/tools/libdatadog_checksums.txt +++ /dev/null @@ -1,5 +0,0 @@ -57f83aff275628bb1af89c22bb4bd696726daf2a9e09b6cd0d966b29e65a7ad6 libdatadog-aarch64-alpine-linux-musl.tar.gz -2be2efa98dfc32f109abdd79242a8e046a7a300c77634135eb293e000ecd4a4c libdatadog-aarch64-apple-darwin.tar.gz -36db8d50ccabb71571158ea13835c0f1d05d30b32135385f97c16343cfb6ddd4 libdatadog-aarch64-unknown-linux-gnu.tar.gz -2f61fd21cf2f8147743e414b4a8c77250a17be3aecc42a69ffe54f0a603d5c92 libdatadog-x86_64-alpine-linux-musl.tar.gz -f01f05600591063eba4faf388f54c155ab4e6302e5776c7855e3734955f7daf7 libdatadog-x86_64-unknown-linux-gnu.tar.gz diff --git a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt index 2ae02df66f2..c23a3e3ddce 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt @@ -10,12 +10,11 @@ message(STATUS "Building extension: ${EXTENSION_NAME}") # Get the cmake modules for this project list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake") -# Includes -include(FetchContent) -include(ExternalProject) -include(FindLibdatadog) - -add_subdirectory(../dd_wrapper ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build) +# Having a common target in a subdirectory like this is a hack and a mistake, but it's fiddly to change it so we haven't +# been able to. Instead, make sure that the binary path set in the subdirectory is stable *as a string* in order to make +# sure the caches work. +get_filename_component(DD_WRAPPER_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build ABSOLUTE) +add_subdirectory(../dd_wrapper ${DD_WRAPPER_BUILD_DIR}) find_package(Python3 COMPONENTS Interpreter Development) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt index 809569d8493..c427abdcfbc 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/CMakeLists.txt @@ -12,15 +12,24 @@ get_filename_component(dd_wrapper_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../ddtr list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") # Includes -include(FetchContent) -include(ExternalProject) -include(FindLibdatadog) include(AnalysisFunc) include(FindClangtidy) include(FindCppcheck) include(FindInfer) include(CheckSymbolExists) +# Load libdatadog +include(FindLibdatadog) + +# Since this file is currently only loaded as a subdirectory, we need to propagate certain libdatadog variables up to +# the parent scope. +set(Datadog_INCLUDE_DIRS + ${Datadog_INCLUDE_DIRS} + PARENT_SCOPE) +set(Datadog_LIBRARIES + ${Datadog_LIBRARIES} + PARENT_SCOPE) + set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -51,7 +60,7 @@ target_include_directories(dd_wrapper PRIVATE include ${Datadog_INCLUDE_DIRS}) target_link_libraries(dd_wrapper PRIVATE ${Datadog_LIBRARIES} Threads::Threads) -# Figure out the suffix. Try to approximate the cpython way of doing things. C library +# Figure out the suffix. Try to approximate the cpython way of doing things. check_symbol_exists(__GLIBC__ "features.h" HAVE_GLIBC) check_symbol_exists(__MUSL__ "features.h" HAVE_MUSL) diff --git a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt index fe92fac3952..6a4cb4e8803 100644 --- a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt @@ -13,14 +13,11 @@ message(STATUS "Building extension: ${EXTENSION_NAME}") # Get the cmake modules for this project list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake") -# Includes -include(FetchContent) -include(ExternalProject) -include(FindLibdatadog) - -# Technically, this should be its own project which we `include()`, but I don't want to deal with that when so many -# things may yet be factored differently. -add_subdirectory(../dd_wrapper ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build) +# Having a common target in a subdirectory like this is a hack and a mistake, but it's fiddly to change it so we haven't +# been able to. Instead, make sure that the binary path set in the subdirectory is stable *as a string* in order to make +# sure the caches work. +get_filename_component(DD_WRAPPER_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build ABSOLUTE) +add_subdirectory(../dd_wrapper ${DD_WRAPPER_BUILD_DIR}) find_package(Python3 COMPONENTS Interpreter Development) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt index 69788494920..77952e09d41 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/CMakeLists.txt @@ -11,16 +11,17 @@ message(STATUS "Building extension: ${EXTENSION_NAME}") # Custom cmake modules are in the parent directory list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +# Having a common target in a subdirectory like this is a hack and a mistake, but it's fiddly to change it so we haven't +# been able to. Instead, make sure that the binary path set in the subdirectory is stable *as a string* in order to make +# sure the caches work. +get_filename_component(DD_WRAPPER_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build ABSOLUTE) +add_subdirectory(../dd_wrapper ${DD_WRAPPER_BUILD_DIR}) + # Includes include(FetchContent) -include(ExternalProject) include(AnalysisFunc) include(FindCppcheck) -# dd_wrapper should be its own project at one point, if the current design is kept, but whether or not we keep that -# design is unknown. Hack it for now. -add_subdirectory(../dd_wrapper ${CMAKE_CURRENT_BINARY_DIR}/../dd_wrapper_build) - find_package(Python3 COMPONENTS Interpreter Development) # Make sure we have necessary Python variables From 28132911ed31192f5d1ea0e78aee30b0f26890c7 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 19 Dec 2024 09:39:39 +0100 Subject: [PATCH 332/372] ci: enable standalone sca system tests (#11769) CI: Enables [system tests for Standalone SCA billing](https://github.com/DataDog/system-tests/pull/3690) to run on dd-trace-py's CI ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/system-tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index ce795db4fe2..ccf6c6501d9 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -213,6 +213,10 @@ jobs: if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' run: ./run.sh IAST_STANDALONE + - name: Run SCA_STANDALONE + if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' + run: ./run.sh SCA_STANDALONE + - name: Run APPSEC_RUNTIME_ACTIVATION if: always() && steps.docker_load.outcome == 'success' && matrix.scenario == 'appsec-1' run: ./run.sh APPSEC_RUNTIME_ACTIVATION From b632a714bae6ab1d5703e1834d31cf9b31ceed27 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 19 Dec 2024 16:13:52 +0000 Subject: [PATCH 333/372] chore(er): correct exception ID field name (#11737) We correct the name of the field that is expected to carry the exception ID. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_exception/replay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 3a54bce6f51..5b9f1a9f330 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -170,7 +170,7 @@ class SpanExceptionSnapshot(Snapshot): @property def data(self) -> t.Dict[str, t.Any]: data = super().data - data.update({"exception-id": str(self.exc_id)}) + data.update({"exceptionId": str(self.exc_id)}) return data From f483beb207f3670318ac01e8b314c02c0de0c070 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 19 Dec 2024 11:28:24 -0500 Subject: [PATCH 334/372] ci: do not use datadog-ci binary (#11789) --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4105e2d5eb0..748942af278 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -93,7 +93,6 @@ check_new_flaky_tests: stage: quality-gate extends: .testrunner script: - - curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci - export DD_SITE=datadoghq.com - export DD_API_KEY=$(aws ssm get-parameter --region us-east-1 --name ci.${CI_PROJECT_NAME}.dd-api-key-qualitygate --with-decryption --query "Parameter.Value" --out text) - export DD_APP_KEY=$(aws ssm get-parameter --region us-east-1 --name ci.${CI_PROJECT_NAME}.dd-app-key-qualitygate --with-decryption --query "Parameter.Value" --out text) @@ -101,4 +100,4 @@ check_new_flaky_tests: except: - main - '[0-9].[0-9]*' - - 'mq-working-branch**' \ No newline at end of file + - 'mq-working-branch**' From 0035bfee97f544c650ee51ba4279cc3df5c99c82 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 19 Dec 2024 16:45:54 +0000 Subject: [PATCH 335/372] chore(di): capture exception chain (#11771) We augment the exception fields with known exception chaining attributes to allow capturing exception chaining relations. The fields need to be added manually because they are part of the BaseException built-in fields and are not included in the object's __dict__ attribute. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_signal/utils.py | 9 +++++++++ tests/debugging/exception/test_replay.py | 4 ++-- tests/debugging/test_debugger.py | 20 +++++++++++++++++++- tests/debugging/test_encoding.py | 16 +++++++++++++++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/ddtrace/debugging/_signal/utils.py b/ddtrace/debugging/_signal/utils.py index b2e5d8e285b..09b319598ef 100644 --- a/ddtrace/debugging/_signal/utils.py +++ b/ddtrace/debugging/_signal/utils.py @@ -304,6 +304,15 @@ def capture_value( } fields = get_fields(value) + + # Capture exception chain for exceptions + if _isinstance(value, BaseException): + for attr in ("args", "__cause__", "__context__", "__suppress_context__"): + try: + fields[attr] = object.__getattribute__(value, attr) + except AttributeError: + pass + captured_fields = { n: ( capture_value(v, level=level - 1, maxlen=maxlen, maxsize=maxsize, maxfields=maxfields, stopping_cond=cond) diff --git a/tests/debugging/exception/test_replay.py b/tests/debugging/exception/test_replay.py index 8b9a2a7d830..54baeb8b826 100644 --- a/tests/debugging/exception/test_replay.py +++ b/tests/debugging/exception/test_replay.py @@ -161,8 +161,8 @@ def b_chain(bar): m = 4 try: a(bar % m) - except ValueError: - raise KeyError("chain it") + except ValueError as exc: + raise KeyError("chain it") from exc def c(foo=42): with self.trace("c"): diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index ed337c27f1e..0cc65bc43cf 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -210,7 +210,25 @@ def test_debugger_function_probe_on_function_with_exception(): return_capture = snapshot_data["captures"]["return"] assert return_capture["arguments"] == {} - assert return_capture["locals"] == {"@exception": {"fields": {}, "type": "Exception"}} + assert return_capture["locals"] == { + "@exception": { + "type": "Exception", + "fields": { + "args": { + "type": "tuple", + "elements": [ + {"type": "str", "value": "'Hello'"}, + {"type": "str", "value": "'world!'"}, + {"type": "int", "value": "42"}, + ], + "size": 3, + }, + "__cause__": {"type": "NoneType", "isNull": True}, + "__context__": {"type": "NoneType", "isNull": True}, + "__suppress_context__": {"type": "bool", "value": "False"}, + }, + } + } assert return_capture["throwable"]["message"] == "'Hello', 'world!', 42" assert return_capture["throwable"]["type"] == "Exception" diff --git a/tests/debugging/test_encoding.py b/tests/debugging/test_encoding.py index c06e5000ed8..c22851f1112 100644 --- a/tests/debugging/test_encoding.py +++ b/tests/debugging/test_encoding.py @@ -191,7 +191,21 @@ def _(): exc = context.pop("throwable") assert context["arguments"] == {} - assert context["locals"] == {"@exception": {"type": "Exception", "fields": {}}} + assert context["locals"] == { + "@exception": { + "type": "Exception", + "fields": { + "args": { + "type": "tuple", + "elements": [{"type": "str", "value": "'test'"}, {"type": "str", "value": "'me'"}], + "size": 2, + }, + "__cause__": {"type": "NoneType", "isNull": True}, + "__context__": {"type": "NoneType", "isNull": True}, + "__suppress_context__": {"type": "bool", "value": "False"}, + }, + } + } assert exc["message"] == "'test', 'me'" assert exc["type"] == "Exception" From 315a48f6f23dd2901533a63ecfff4d1d11daee03 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 19 Dec 2024 16:46:19 +0000 Subject: [PATCH 336/372] chore(er): include exception hash (#11772) We include the span tag that carries the exception hash, according to the RFC. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_exception/replay.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 5b9f1a9f330..080b4cbfc61 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -40,7 +40,8 @@ CAPTURE_TRACE_TAG = "_dd.debug.error.trace_captured" # unique exception id -EXCEPTION_ID_TAG = "_dd.debug.error.exception_id" +EXCEPTION_HASH_TAG = "_dd.debug.error.exception_hash" +EXCEPTION_ID_TAG = "_dd.debug.error.exception_capture_id" # link to matching snapshot for every frame in the traceback FRAME_SNAPSHOT_ID_TAG = "_dd.debug.error.%d.snapshot_id" @@ -80,9 +81,8 @@ def exception_chain_ident(chain: ExceptionChain) -> int: return h -def limit_exception(chain: ExceptionChain) -> bool: +def limit_exception(exc_ident: int) -> bool: try: - exc_ident = exception_chain_ident(chain) hg = EXCEPTION_IDENT_LIMITER.get(exc_ident) if hg is None: # We haven't seen this exception yet, or it's been evicted @@ -218,7 +218,8 @@ def on_span_exception( # No exceptions to capture return - if limit_exception(chain): + exc_ident = exception_chain_ident(chain) + if limit_exception(exc_ident): # We have seen this exception recently return @@ -272,6 +273,7 @@ def on_span_exception( _tb = _tb.tb_next span.set_tag_str(DEBUG_INFO_TAG, "true") + span.set_tag_str(EXCEPTION_HASH_TAG, str(exc_ident)) span.set_tag_str(EXCEPTION_ID_TAG, str(exc_id)) @classmethod From e9dbe4f74e5ebf607c51ba17f854fc3c2b219f64 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 19 Dec 2024 13:02:51 -0500 Subject: [PATCH 337/372] ci: wait for dependent services before running tests (#11780) --- .gitlab/tests.yml | 13 +++-- .riot/requirements/151d7b0.txt | 41 ++++++++++++++ .riot/requirements/1805689.txt | 37 ------------- hatch.toml | 2 +- riotfile.py | 1 + scripts/gen_gitlab_config.py | 21 ++++---- tests/suitespec.yml | 6 +++ tests/wait-for-services.py | 98 ++++++++++++++++++++++++++-------- 8 files changed, 145 insertions(+), 74 deletions(-) create mode 100644 .riot/requirements/151d7b0.txt delete mode 100644 .riot/requirements/1805689.txt diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index ce1fb8fd0ad..d38a22cf0ff 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -1,7 +1,7 @@ stages: - precheck - - hatch - riot + - hatch variables: RIOT_RUN_CMD: riot -P -v run --exitfirst --pass-env -s @@ -30,6 +30,9 @@ variables: parallel: 4 # DEV: This is the max retries that GitLab currently allows for retry: 2 + before_script: + - !reference [.testrunner, before_script] + - pip install riot==0.20.1 script: - export PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --ddtrace" - export _DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDER=true @@ -51,7 +54,7 @@ variables: services: - !reference [.services, testagent] before_script: - - !reference [.testrunner, before_script] + - !reference [.test_base_hatch, before_script] # DEV: All job variables get shared with services, setting `DD_TRACE_AGENT_URL` on the testagent will tell it to forward all requests to the # agent at that host. Therefore setting this as a variable will cause recursive requests to the testagent - export DD_TRACE_AGENT_URL="http://testagent:9126" @@ -88,12 +91,14 @@ build_base_venvs: - !reference [.services, ddagent] # DEV: This is the max retries that GitLab currently allows for retry: 2 - script: + before_script: + - !reference [.testrunner, before_script] - pip install riot==0.20.1 - unset DD_SERVICE - unset DD_ENV - unset DD_TAGS - unset DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED + script: - | hashes=( $(riot list --hash-only "${SUITE_NAME}" | sort | ./.gitlab/ci-split-input.sh) ) if [[ ${#hashes[@]} -eq 0 ]]; then @@ -116,7 +121,7 @@ build_base_venvs: - !reference [.test_base_riot, services] - !reference [.services, testagent] before_script: - - !reference [.testrunner, before_script] + - !reference [.test_base_riot, before_script] # DEV: All job variables get shared with services, setting `DD_TRACE_AGENT_URL` on the testagent will tell it to forward all requests to the # agent at that host. Therefore setting this as a variable will cause recursive requests to the testagent - export DD_TRACE_AGENT_URL="http://testagent:9126" diff --git a/.riot/requirements/151d7b0.txt b/.riot/requirements/151d7b0.txt new file mode 100644 index 00000000000..9593b418017 --- /dev/null +++ b/.riot/requirements/151d7b0.txt @@ -0,0 +1,41 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/151d7b0.in +# +amqp==2.6.1 +attrs==24.3.0 +cassandra-driver==3.29.2 +certifi==2024.12.14 +charset-normalizer==3.4.0 +click==8.1.7 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +future==1.0.0 +geomet==0.2.1.post1 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +kombu==4.2.2.post1 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +pytz==2024.2 +requests==2.32.3 +six==1.17.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +urllib3==2.2.3 +vertica-python==0.6.14 +vine==1.3.0 +zipp==3.21.0 diff --git a/.riot/requirements/1805689.txt b/.riot/requirements/1805689.txt deleted file mode 100644 index e76e16e1946..00000000000 --- a/.riot/requirements/1805689.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1805689.in -# -amqp==2.6.1 -attrs==23.1.0 -cassandra-driver==3.28.0 -click==8.1.7 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -future==0.18.3 -geomet==0.2.1.post1 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -kombu==4.2.2.post1 -mock==5.1.0 -mysql-connector-python==8.2.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -protobuf==4.21.12 -psycopg2-binary==2.9.9 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -vertica-python==0.6.14 -vine==1.3.0 -zipp==3.17.0 diff --git a/hatch.toml b/hatch.toml index 7dae1538613..ff11ec3f743 100644 --- a/hatch.toml +++ b/hatch.toml @@ -124,7 +124,7 @@ extra-dependencies = [ ] [envs.slotscheck.scripts] -_ = [ +test = [ "python -m slotscheck -v ddtrace/", ] diff --git a/riotfile.py b/riotfile.py index 6db9102786f..9b1ba5497e7 100644 --- a/riotfile.py +++ b/riotfile.py @@ -586,6 +586,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "vertica-python": ">=0.6.0,<0.7.0", "kombu": ">=4.2.0,<4.3.0", "pytest-randomly": latest, + "requests": latest, }, ), Venv( diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index c868b0f1c86..22b236ddfc5 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -15,7 +15,7 @@ class JobSpec: runner: str pattern: t.Optional[str] = None snapshot: bool = False - services: t.Optional[t.Set[str]] = None + services: t.Optional[t.List[str]] = None env: t.Optional[t.Dict[str, str]] = None parallelism: t.Optional[int] = None retry: t.Optional[int] = None @@ -32,16 +32,25 @@ def __str__(self) -> str: lines.append(f"{self.name}:") lines.append(f" extends: {base}") - if self.services: + services = set(self.services or []) + if services: lines.append(" services:") - _services = [f"!reference [.services, {_}]" for _ in self.services] + _services = [f"!reference [.services, {_}]" for _ in services] if self.snapshot: _services.insert(0, f"!reference [{base}, services]") for service in _services: lines.append(f" - {service}") + wait_for: t.Set[str] = services.copy() + if self.snapshot: + wait_for.add("testagent") + if wait_for: + lines.append(" before_script:") + lines.append(f" - !reference [{base}, before_script]") + lines.append(f" - riot -v run -s --pass-env wait -- {' '.join(wait_for)}") + env = self.env if not env or "SUITE_NAME" not in env: env = env or {} @@ -89,7 +98,6 @@ def gen_required_suites() -> None: TESTS_GEN.write_text( (GITLAB / "tests.yml").read_text().replace(r"{{services.yml}}", (GITLAB / "services.yml").read_text()) ) - # Generate the list of suites to run with TESTS_GEN.open("a") as f: for suite in required_suites: @@ -159,11 +167,6 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: command="hatch run meta-testing:meta-testing", paths={"**conftest.py"}, ) - check( - name="slotscheck", - command="hatch run slotscheck:_", - paths={"**.py"}, - ) # ----------------------------------------------------------------------------- diff --git a/tests/suitespec.yml b/tests/suitespec.yml index c6a89720676..4b13005d662 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -195,6 +195,12 @@ suites: - tests/cache/* runner: riot snapshot: true + slotscheck: + parallelism: 1 + paths: + - 'ddtrace/**/*.py' + runner: hatch + snapshot: false profile: env: DD_TRACE_AGENT_URL: '' diff --git a/tests/wait-for-services.py b/tests/wait-for-services.py index 2f3fc29e7b3..048cb6948a8 100644 --- a/tests/wait-for-services.py +++ b/tests/wait-for-services.py @@ -1,10 +1,16 @@ +import logging +import os import sys import time +import typing as t from cassandra.cluster import Cluster from cassandra.cluster import NoHostAvailable from contrib.config import CASSANDRA_CONFIG +from contrib.config import ELASTICSEARCH_CONFIG +from contrib.config import HTTPBIN_CONFIG from contrib.config import MYSQL_CONFIG +from contrib.config import OPENSEARCH_CONFIG from contrib.config import POSTGRES_CONFIG from contrib.config import RABBITMQ_CONFIG from contrib.config import VERTICA_CONFIG @@ -12,72 +18,83 @@ import mysql.connector from psycopg2 import OperationalError from psycopg2 import connect +import requests import vertica_python -def try_until_timeout(exception): +logging.basicConfig(level=logging.INFO) +log = logging.getLogger(__name__) + + +def try_until_timeout(exception, tries: int = 100, timeout: float = 0.2, args: t.Optional[t.Dict[str, t.Any]] = None): """Utility decorator that tries to call a check until there is a timeout. The default timeout is about 20 seconds. """ + if not args: + args = {} def wrap(fn): - def wrapper(*args, **kwargs): + def wrapper(**kwargs): err = None - for _ in range(100): + _kwargs = args.copy() + _kwargs.update(kwargs) + + for i in range(tries): try: - fn() + log.info("Attempt %d: %s(%r)", i, fn.__name__, _kwargs) + fn(**_kwargs) except exception as e: err = e - time.sleep(0.2) + time.sleep(timeout) else: break else: if err: raise err + log.info("Succeeded: %s", fn.__name__) return wrapper return wrap -@try_until_timeout(OperationalError) -def check_postgres(): - conn = connect(**POSTGRES_CONFIG) +@try_until_timeout(OperationalError, args={"pg_config": POSTGRES_CONFIG}) +def check_postgres(pg_config): + conn = connect(**pg_config) try: conn.cursor().execute("SELECT 1;") finally: conn.close() -@try_until_timeout(NoHostAvailable) -def check_cassandra(): - with Cluster(**CASSANDRA_CONFIG).connect() as conn: +@try_until_timeout(NoHostAvailable, args={"cassandra_config": CASSANDRA_CONFIG}) +def check_cassandra(cassandra_config): + with Cluster(**cassandra_config).connect() as conn: conn.execute("SELECT now() FROM system.local") -@try_until_timeout(Exception) -def check_mysql(): - conn = mysql.connector.connect(**MYSQL_CONFIG) +@try_until_timeout(Exception, args={"mysql_config": MYSQL_CONFIG}) +def check_mysql(mysql_config): + conn = mysql.connector.connect(**mysql_config) try: conn.cursor().execute("SELECT 1;") finally: conn.close() -@try_until_timeout(Exception) -def check_vertica(): - conn = vertica_python.connect(**VERTICA_CONFIG) +@try_until_timeout(Exception, args={"vertica_config": VERTICA_CONFIG}) +def check_vertica(vertica_config): + conn = vertica_python.connect(**vertica_config) try: conn.cursor().execute("SELECT 1;") finally: conn.close() -@try_until_timeout(Exception) -def check_rabbitmq(): - url = "amqp://{user}:{password}@{host}:{port}//".format(**RABBITMQ_CONFIG) +@try_until_timeout(Exception, args={"url": "amqp://{user}:{password}@{host}:{port}//".format(**RABBITMQ_CONFIG)}) +def check_rabbitmq(url): conn = kombu.Connection(url) try: conn.connect() @@ -85,17 +102,52 @@ def check_rabbitmq(): conn.release() +@try_until_timeout(Exception, args={"url": os.environ.get("DD_TRACE_AGENT_URL", "http://localhost:8126")}) +def check_agent(url): + if not url.endswith("/"): + url += "/" + + res = requests.get(url) + if res.status_code not in (404, 200): + raise Exception("Agent not ready") + + +@try_until_timeout(Exception, args={"url": "http://{host}:{port}/".format(**ELASTICSEARCH_CONFIG)}) +def check_elasticsearch(url): + requests.get(url).raise_for_status() + + +@try_until_timeout( + Exception, tries=120, timeout=1, args={"url": "http://{host}:{port}/".format(**OPENSEARCH_CONFIG)} +) # 2 minutes, OpenSearch is slow to start +def check_opensearch(url): + requests.get(url).raise_for_status() + + +@try_until_timeout(Exception, args={"url": "http://{host}:{port}/".format(**HTTPBIN_CONFIG)}) +def check_httpbin(url): + requests.get(url).raise_for_status() + + if __name__ == "__main__": check_functions = { "cassandra": check_cassandra, - "postgres": check_postgres, + "ddagent": check_agent, + "elasticsearch": check_elasticsearch, + "httpbin_local": check_httpbin, "mysql": check_mysql, - "vertica": check_vertica, + "opensearch": check_opensearch, + "postgres": check_postgres, "rabbitmq": check_rabbitmq, + "testagent": check_agent, + "vertica": check_vertica, } if len(sys.argv) >= 2: for service in sys.argv[1:]: - check_functions[service]() + if service not in check_functions: + log.warning("Unknown service: %s", service) + else: + check_functions[service]() else: print("usage: python {} SERVICE_NAME".format(sys.argv[0])) sys.exit(1) From 89d82c3f11305f0ae4025fa5f7349342846b1bd2 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:15:25 -0800 Subject: [PATCH 338/372] docs: add details to the release note about 3.13 (#11792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change lists everything that is currently known not to work with Python 3.13 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Vítor De Araújo --- .../notes/threethirteen-d40d659d8939fe5e.yaml | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml b/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml index 837858691fe..3a229695abd 100644 --- a/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml +++ b/releasenotes/notes/threethirteen-d40d659d8939fe5e.yaml @@ -1,4 +1,52 @@ --- upgrade: - | - Makes the library compatible with Python 3.13 + Makes the library compatible with Python 3.13. + + The following limitations currently apply to support for Python 3.13: + - ``ddtrace`` is not supported on Windows with Python 3.13 + - Appsec Threat Detection is not tested against Django, Flask, or FastAPI with 3.13 + - Automatic Service Naming is not tested with 3.13 + - The ``ddtrace-run`` entrypoint is not tested with 3.13 + - The following products are not tested with 3.13: + - Code Coverage + - Appsec IAST + - Data Streams Monitoring + - CI Visibility + - Continuous Profiling + - The following integrations are not tested with 3.13: + - aiobotocore + - aiomysql + - aiopg + - anthropic + - asyncpg + - avro + - botocore + - confluent-kafka + - consul + - django + - falcon + - fastapi + - freezegun + - gevent + - google_generativeai + - grpcio + - gunicorn + - langchain + - mysqlclient + - opentracing + - protobuf + - psycopg + - psycopg2 + - pymysql + - pyodbc + - pytest + - pytest-bdd + - pytest-benchmark + - sanic + - selenium + - sqlalchemy + - sqlite3 + - starlette + - tornado + - vertexai From 79069a3b41828cf194c279dcfc6b155a64f8b080 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 19 Dec 2024 13:31:41 -0500 Subject: [PATCH 339/372] fix(library): catch exceptions raised while enabling ddtrace integrations (#11759) ## Description - Improves the error message generated when `ddtrace` failed to patch/enable an integration. - Ensure patching modules and sub-modules are wrapped in a try-except. The ddtrace library should not crash an application if an integration can not be patched. ## Motivation Prevent issues like this: https://github.com/DataDog/dd-trace-py/issues/11603 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/_monkey.py | 16 +++++++++------- ...efactor-patch-error-ssi-1a2e9fe206d6d6df.yaml | 4 ++++ tests/telemetry/test_telemetry.py | 6 ++---- 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/refactor-patch-error-ssi-1a2e9fe206d6d6df.yaml diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 8dd83558c83..488211e46b1 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -173,17 +173,22 @@ def on_import(hook): path = "%s.%s" % (prefix, module) try: imported_module = importlib.import_module(path) + imported_module.patch() + if hasattr(imported_module, "patch_submodules"): + imported_module.patch_submodules(patch_indicator) except Exception as e: if raise_errors: raise - error_msg = "failed to import ddtrace module %r when patching on import" % (path,) - log.error(error_msg, exc_info=True) - telemetry.telemetry_writer.add_integration(module, False, PATCH_MODULES.get(module) is True, error_msg) + log.error( + "failed to enable ddtrace support for %s: %s", + module, + str(e), + ) + telemetry.telemetry_writer.add_integration(module, False, PATCH_MODULES.get(module) is True, str(e)) telemetry.telemetry_writer.add_count_metric( "tracers", "integration_errors", 1, (("integration_name", module), ("error_type", type(e).__name__)) ) else: - imported_module.patch() if hasattr(imported_module, "get_versions"): versions = imported_module.get_versions() for name, v in versions.items(): @@ -196,9 +201,6 @@ def on_import(hook): module, True, PATCH_MODULES.get(module) is True, "", version=version ) - if hasattr(imported_module, "patch_submodules"): - imported_module.patch_submodules(patch_indicator) - return on_import diff --git a/releasenotes/notes/refactor-patch-error-ssi-1a2e9fe206d6d6df.yaml b/releasenotes/notes/refactor-patch-error-ssi-1a2e9fe206d6d6df.yaml new file mode 100644 index 00000000000..8afc2e7595f --- /dev/null +++ b/releasenotes/notes/refactor-patch-error-ssi-1a2e9fe206d6d6df.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Integrations: Improved error handling for exceptions raised during the startup of ddtrace integrations. This reduces the likelihood of the ddtrace library raising unhandled exceptions. \ No newline at end of file diff --git a/tests/telemetry/test_telemetry.py b/tests/telemetry/test_telemetry.py index d767090f6d2..558e9961afc 100644 --- a/tests/telemetry/test_telemetry.py +++ b/tests/telemetry/test_telemetry.py @@ -243,14 +243,12 @@ def test_handled_integration_error(test_agent_session, run_python_code_in_subpro _, stderr, status, _ = run_python_code_in_subprocess(code, env=env) assert status == 0, stderr - expected_stderr = b"failed to import" - assert expected_stderr in stderr + assert b"failed to enable ddtrace support for sqlite3" in stderr integrations_events = test_agent_session.get_events("app-integrations-change", subprocess=True) assert len(integrations_events) == 1 assert ( - integrations_events[0]["payload"]["integrations"][0]["error"] - == "failed to import ddtrace module 'ddtrace.contrib.sqlite3' when patching on import" + integrations_events[0]["payload"]["integrations"][0]["error"] == "module 'sqlite3' has no attribute 'connect'" ) # Get metric containing the integration error From 90dbdd33c362001325eb15ead8fa6e664ecb1753 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:41:45 -0800 Subject: [PATCH 340/372] chore: enable tests under 3.13 for ddtrace-run (#11793) Enable tests of ddtrace-run against Py3.13, with Profiling enablement skipped because Profiling doesn't support 3.13 yet. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/afc1791.txt | 27 +++++++++++++++++++ .../313-ddtracerun-e34ef8d7496091b3.yaml | 4 +++ riotfile.py | 2 +- tests/commands/test_runner.py | 1 + 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 .riot/requirements/afc1791.txt create mode 100644 releasenotes/notes/313-ddtracerun-e34ef8d7496091b3.yaml diff --git a/.riot/requirements/afc1791.txt b/.riot/requirements/afc1791.txt new file mode 100644 index 00000000000..2a3cfd4447d --- /dev/null +++ b/.riot/requirements/afc1791.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/afc1791.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +gevent==24.11.1 +greenlet==3.1.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +redis==5.2.1 +sortedcontainers==2.4.0 +zope-event==5.0 +zope-interface==7.2 + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/releasenotes/notes/313-ddtracerun-e34ef8d7496091b3.yaml b/releasenotes/notes/313-ddtracerun-e34ef8d7496091b3.yaml new file mode 100644 index 00000000000..50cf1a7d196 --- /dev/null +++ b/releasenotes/notes/313-ddtracerun-e34ef8d7496091b3.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Enables tests of the ``ddtrace-run`` entrypoint with Python 3.13 diff --git a/riotfile.py b/riotfile.py index 9b1ba5497e7..e7a078a5425 100644 --- a/riotfile.py +++ b/riotfile.py @@ -521,7 +521,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} --no-cov tests/commands/test_runner.py", venvs=[ Venv( - pys=select_pys(max_version="3.12"), + pys=select_pys(), pkgs={ "redis": latest, "gevent": latest, diff --git a/tests/commands/test_runner.py b/tests/commands/test_runner.py index 8c5dd0bd7f8..b6ad3cbd755 100644 --- a/tests/commands/test_runner.py +++ b/tests/commands/test_runner.py @@ -229,6 +229,7 @@ def test_debug_mode(self): assert b"debug mode has been enabled for the ddtrace logger" in p.stderr.read() +@pytest.mark.skipif(sys.version_info > (3, 12), reason="Profiling unsupported with 3.13") def test_env_profiling_enabled(monkeypatch): """DD_PROFILING_ENABLED allows enabling the global profiler.""" # Off by default From 099247ef10b10f4b879f825e45929511b09a3668 Mon Sep 17 00:00:00 2001 From: kyle Date: Thu, 19 Dec 2024 14:37:06 -0500 Subject: [PATCH 341/372] chore(llmobs): refactor trace processor tests (#11784) The trace processor tests intermingled business logic with the implementation of the trace processor. To separate them out, we introduce a few testing fixtures useful for dealing with tracing and capturing llm obs events. This reduces the amount of mocking to zero and allows us to test more realistically. There's a bit of a nasty hack to make sure the configs are updated in llmobs modules that grab a reference to it but I'll follow up to clean that up as well. This reduces most of the tests from this: ```python def test_input_parameters_are_set(): """Test that input parameters are set on the span event if they are present on the span.""" dummy_tracer = DummyTracer() mock_llmobs_span_writer = mock.MagicMock() with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: llm_span._set_ctx_item(SPAN_KIND, "llm") llm_span._set_ctx_item(INPUT_PARAMETERS, {"key": "value"}) tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["parameters"] == {"key": "value"} ``` to this: ```python def test_input_parameters_are_set(tracer, llmobs_events): """Test that input parameters are set on the span event if they are present on the span.""" with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: llm_span._set_ctx_item(const.SPAN_KIND, "llm") llm_span._set_ctx_item(const.INPUT_PARAMETERS, {"key": "value"}) assert llmobs_events[0]["meta"]["input"]["parameters"] == {"key": "value"} ``` --- tests/llmobs/conftest.py | 47 +++ tests/llmobs/test_llmobs.py | 254 +++++++++++++ tests/llmobs/test_llmobs_trace_processor.py | 373 -------------------- 3 files changed, 301 insertions(+), 373 deletions(-) create mode 100644 tests/llmobs/test_llmobs.py diff --git a/tests/llmobs/conftest.py b/tests/llmobs/conftest.py index 0b0ce8b7964..a7d467b3985 100644 --- a/tests/llmobs/conftest.py +++ b/tests/llmobs/conftest.py @@ -6,6 +6,7 @@ from ddtrace.internal.utils.http import Response from ddtrace.llmobs import LLMObs as llmobs_service from ddtrace.llmobs._evaluators.ragas.faithfulness import RagasFaithfulnessEvaluator +from ddtrace.llmobs._writer import LLMObsSpanWriter from tests.llmobs._utils import logs_vcr from tests.utils import DummyTracer from tests.utils import override_env @@ -212,3 +213,49 @@ def mock_ragas_evaluator(mock_llmobs_eval_metric_writer, ragas): LLMObsMockRagas.return_value = 1.0 yield RagasFaithfulnessEvaluator patcher.stop() + + +@pytest.fixture +def tracer(): + return DummyTracer() + + +@pytest.fixture +def llmobs_env(): + return { + "DD_API_KEY": "", + "DD_LLMOBS_ML_APP": "unnamed-ml-app", + } + + +class TestLLMObsSpanWriter(LLMObsSpanWriter): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.events = [] + + def enqueue(self, event): + self.events.append(event) + + +@pytest.fixture +def llmobs_span_writer(): + yield TestLLMObsSpanWriter(interval=1.0, timeout=1.0) + + +@pytest.fixture +def llmobs(monkeypatch, tracer, llmobs_env, llmobs_span_writer): + for env, val in llmobs_env.items(): + monkeypatch.setenv(env, val) + + # TODO: remove once rest of tests are moved off of global config tampering + with override_global_config(dict(_llmobs_ml_app=llmobs_env.get("DD_LLMOBS_ML_APP"))): + llmobs_service.enable(_tracer=tracer) + llmobs_service._instance._llmobs_span_writer = llmobs_span_writer + llmobs_service._instance._trace_processor._span_writer = llmobs_span_writer + yield llmobs + llmobs_service.disable() + + +@pytest.fixture +def llmobs_events(llmobs, llmobs_span_writer): + return llmobs_span_writer.events diff --git a/tests/llmobs/test_llmobs.py b/tests/llmobs/test_llmobs.py new file mode 100644 index 00000000000..1bae7efe9ed --- /dev/null +++ b/tests/llmobs/test_llmobs.py @@ -0,0 +1,254 @@ +import mock +import pytest + +from ddtrace.ext import SpanTypes +from ddtrace.llmobs import _constants as const +from ddtrace.llmobs._utils import _get_llmobs_parent_id +from ddtrace.llmobs._utils import _get_session_id +from tests.llmobs._utils import _expected_llmobs_llm_span_event + + +@pytest.fixture +def mock_logs(): + with mock.patch("ddtrace.llmobs._trace_processor.log") as mock_logs: + yield mock_logs + + +class TestMLApp: + @pytest.mark.parametrize("llmobs_env", [{"DD_LLMOBS_ML_APP": ""}]) + def test_tag_defaults_to_env_var(self, tracer, llmobs_env, llmobs_events): + """Test that no ml_app defaults to the environment variable DD_LLMOBS_ML_APP.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + assert "ml_app:" in llmobs_events[0]["tags"] + + @pytest.mark.parametrize("llmobs_env", [{"DD_LLMOBS_ML_APP": ""}]) + def test_tag_overrides_env_var(self, tracer, llmobs_env, llmobs_events): + """Test that when ml_app is set on the span, it overrides the environment variable DD_LLMOBS_ML_APP.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.ML_APP, "test-ml-app") + assert "ml_app:test-ml-app" in llmobs_events[0]["tags"] + + def test_propagates_ignore_non_llmobs_spans(self, tracer, llmobs_events): + """ + Test that when ml_app is not set, we propagate from nearest LLMObs ancestor + even if there are non-LLMObs spans in between. + """ + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.ML_APP, "test-ml-app") + with tracer.trace("child_span"): + with tracer.trace("llm_grandchild_span", span_type=SpanTypes.LLM) as grandchild_span: + grandchild_span._set_ctx_item(const.SPAN_KIND, "llm") + with tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: + great_grandchild_span._set_ctx_item(const.SPAN_KIND, "llm") + assert len(llmobs_events) == 3 + for llmobs_event in llmobs_events: + assert "ml_app:test-ml-app" in llmobs_event["tags"] + + +def test_set_correct_parent_id(tracer): + """Test that the parent_id is set as the span_id of the nearest LLMObs span in the span's ancestor tree.""" + with tracer.trace("root"): + with tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: + pass + assert _get_llmobs_parent_id(llm_span) is None + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: + with tracer.trace("child_span") as child_span: + with tracer.trace("llm_span", span_type=SpanTypes.LLM) as grandchild_span: + pass + assert _get_llmobs_parent_id(root_span) is None + assert _get_llmobs_parent_id(child_span) == str(root_span.span_id) + assert _get_llmobs_parent_id(grandchild_span) == str(root_span.span_id) + + +class TestSessionId: + def test_propagate_from_ancestors(self, tracer): + """ + Test that session_id is propagated from the nearest LLMObs span in the span's ancestor tree + if no session_id is not set on the span itself. + """ + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: + root_span._set_ctx_item(const.SESSION_ID, "test_session_id") + with tracer.trace("child_span"): + with tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: + pass + assert _get_session_id(llm_span) == "test_session_id" + + def test_if_set_manually(self, tracer): + """Test that session_id is extracted from the span if it is already set manually.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: + root_span._set_ctx_item(const.SESSION_ID, "test_session_id") + with tracer.trace("child_span"): + with tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SESSION_ID, "test_different_session_id") + assert _get_session_id(llm_span) == "test_different_session_id" + + def test_propagates_ignore_non_llmobs_spans(self, tracer, llmobs_events): + """ + Test that when session_id is not set, we propagate from nearest LLMObs ancestor + even if there are non-LLMObs spans in between. + """ + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.SESSION_ID, "session-123") + with tracer.trace("child_span"): + with tracer.trace("llm_grandchild_span", span_type=SpanTypes.LLM) as grandchild_span: + grandchild_span._set_ctx_item(const.SPAN_KIND, "llm") + with tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: + great_grandchild_span._set_ctx_item(const.SPAN_KIND, "llm") + + llm_event, grandchild_event, great_grandchild_event = llmobs_events + assert llm_event["session_id"] == "session-123" + assert grandchild_event["session_id"] == "session-123" + assert great_grandchild_event["session_id"] == "session-123" + + +def test_input_value_is_set(tracer, llmobs_events): + """Test that input value is set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.INPUT_VALUE, "value") + assert llmobs_events[0]["meta"]["input"]["value"] == "value" + + +def test_input_messages_are_set(tracer, llmobs_events): + """Test that input messages are set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.INPUT_MESSAGES, [{"content": "message", "role": "user"}]) + assert llmobs_events[0]["meta"]["input"]["messages"] == [{"content": "message", "role": "user"}] + + +def test_input_parameters_are_set(tracer, llmobs_events): + """Test that input parameters are set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.INPUT_PARAMETERS, {"key": "value"}) + assert llmobs_events[0]["meta"]["input"]["parameters"] == {"key": "value"} + + +def test_output_messages_are_set(tracer, llmobs_events): + """Test that output messages are set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.OUTPUT_MESSAGES, [{"content": "message", "role": "user"}]) + assert llmobs_events[0]["meta"]["output"]["messages"] == [{"content": "message", "role": "user"}] + + +def test_output_value_is_set(tracer, llmobs_events): + """Test that output value is set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.OUTPUT_VALUE, "value") + assert llmobs_events[0]["meta"]["output"]["value"] == "value" + + +def test_prompt_is_set(tracer, llmobs_events): + """Test that prompt is set on the span event if they are present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.INPUT_PROMPT, {"variables": {"var1": "var2"}}) + assert llmobs_events[0]["meta"]["input"]["prompt"] == {"variables": {"var1": "var2"}} + + +def test_prompt_is_not_set_for_non_llm_spans(tracer, llmobs_events): + """Test that prompt is NOT set on the span event if the span is not an LLM span.""" + with tracer.trace("task_span", span_type=SpanTypes.LLM) as task_span: + task_span._set_ctx_item(const.SPAN_KIND, "task") + task_span._set_ctx_item(const.INPUT_VALUE, "ival") + task_span._set_ctx_item(const.INPUT_PROMPT, {"variables": {"var1": "var2"}}) + assert llmobs_events[0]["meta"]["input"].get("prompt") is None + + +def test_metadata_is_set(tracer, llmobs_events): + """Test that metadata is set on the span event if it is present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.METADATA, {"key": "value"}) + assert llmobs_events[0]["meta"]["metadata"] == {"key": "value"} + + +def test_metrics_are_set(tracer, llmobs_events): + """Test that metadata is set on the span event if it is present on the span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.METRICS, {"tokens": 100}) + assert llmobs_events[0]["metrics"] == {"tokens": 100} + + +def test_langchain_span_name_is_set_to_class_name(tracer, llmobs_events): + """Test span names for langchain auto-instrumented spans is set correctly.""" + with tracer.trace(const.LANGCHAIN_APM_SPAN_NAME, resource="expected_name", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + assert llmobs_events[0]["name"] == "expected_name" + + +def test_error_is_set(tracer, llmobs_events): + """Test that error is set on the span event if it is present on the span.""" + with pytest.raises(ValueError): + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + raise ValueError("error") + span_event = llmobs_events[0] + assert span_event["meta"]["error.message"] == "error" + assert "ValueError" in span_event["meta"]["error.type"] + assert 'raise ValueError("error")' in span_event["meta"]["error.stack"] + + +def test_model_provider_defaults_to_custom(tracer, llmobs_events): + """Test that model provider defaults to "custom" if not provided.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.MODEL_NAME, "model_name") + span_event = llmobs_events[0] + assert span_event["meta"]["model_name"] == "model_name" + assert span_event["meta"]["model_provider"] == "custom" + + +def test_model_not_set_if_not_llm_kind_span(tracer, llmobs_events): + """Test that model name and provider not set if non-LLM span.""" + with tracer.trace("root_workflow_span", span_type=SpanTypes.LLM) as span: + span._set_ctx_item(const.SPAN_KIND, "workflow") + span._set_ctx_item(const.MODEL_NAME, "model_name") + span_event = llmobs_events[0] + assert "model_name" not in span_event["meta"] + assert "model_provider" not in span_event["meta"] + + +def test_model_and_provider_are_set(tracer, llmobs_events): + """Test that model and provider are set on the span event if they are present on the LLM-kind span.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + llm_span._set_ctx_item(const.SPAN_KIND, "llm") + llm_span._set_ctx_item(const.MODEL_NAME, "model_name") + llm_span._set_ctx_item(const.MODEL_PROVIDER, "model_provider") + span_event = llmobs_events[0] + assert span_event["meta"]["model_name"] == "model_name" + assert span_event["meta"]["model_provider"] == "model_provider" + + +def test_malformed_span_logs_error_instead_of_raising(mock_logs, tracer, llmobs_events): + """Test that a trying to create a span event from a malformed span will log an error instead of crashing.""" + with tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: + # span does not have SPAN_KIND tag + pass + mock_logs.error.assert_called_once_with( + "Error generating LLMObs span event for span %s, likely due to malformed span", llm_span + ) + assert len(llmobs_events) == 0 + + +def test_processor_only_creates_llmobs_span_event(tracer, llmobs_events): + """Test that the LLMObsTraceProcessor only creates LLMObs span events for LLM span types.""" + with tracer.trace("root_llm_span", service="tests.llmobs", span_type=SpanTypes.LLM) as root_span: + root_span._set_ctx_item(const.SPAN_KIND, "llm") + with tracer.trace("child_span"): + with tracer.trace("llm_span", span_type=SpanTypes.LLM) as grandchild_span: + grandchild_span._set_ctx_item(const.SPAN_KIND, "llm") + expected_grandchild_llmobs_span = _expected_llmobs_llm_span_event(grandchild_span, "llm") + expected_grandchild_llmobs_span["parent_id"] = str(root_span.span_id) + + assert len(llmobs_events) == 2 + assert llmobs_events[0] == _expected_llmobs_llm_span_event(root_span, "llm") + assert llmobs_events[1] == expected_grandchild_llmobs_span diff --git a/tests/llmobs/test_llmobs_trace_processor.py b/tests/llmobs/test_llmobs_trace_processor.py index 8eb4c4d6fb3..b55286d49c8 100644 --- a/tests/llmobs/test_llmobs_trace_processor.py +++ b/tests/llmobs/test_llmobs_trace_processor.py @@ -1,36 +1,12 @@ import mock -import pytest from ddtrace._trace.span import Span from ddtrace.ext import SpanTypes -from ddtrace.llmobs._constants import INPUT_MESSAGES -from ddtrace.llmobs._constants import INPUT_PARAMETERS -from ddtrace.llmobs._constants import INPUT_PROMPT -from ddtrace.llmobs._constants import INPUT_VALUE -from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME -from ddtrace.llmobs._constants import METADATA -from ddtrace.llmobs._constants import METRICS -from ddtrace.llmobs._constants import ML_APP -from ddtrace.llmobs._constants import MODEL_NAME -from ddtrace.llmobs._constants import MODEL_PROVIDER -from ddtrace.llmobs._constants import OUTPUT_MESSAGES -from ddtrace.llmobs._constants import OUTPUT_VALUE -from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._trace_processor import LLMObsTraceProcessor -from ddtrace.llmobs._utils import _get_llmobs_parent_id -from ddtrace.llmobs._utils import _get_session_id -from tests.llmobs._utils import _expected_llmobs_llm_span_event -from tests.utils import DummyTracer from tests.utils import override_global_config -@pytest.fixture -def mock_logs(): - with mock.patch("ddtrace.llmobs._trace_processor.log") as mock_logs: - yield mock_logs - - def test_processor_returns_all_traces_by_default(): """Test that the LLMObsTraceProcessor returns all traces by default.""" trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock.MagicMock()) @@ -58,352 +34,3 @@ def test_processor_returns_none_in_agentless_mode(): root_llm_span._set_ctx_item(SPAN_KIND, "llm") trace1 = [root_llm_span] assert trace_filter.process_trace(trace1) is None - - -def test_processor_creates_llmobs_span_event(): - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - mock_llmobs_span_writer = mock.MagicMock() - trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - root_llm_span = Span(name="root", span_type=SpanTypes.LLM) - root_llm_span._set_ctx_item(SPAN_KIND, "llm") - trace = [root_llm_span] - trace_filter.process_trace(trace) - assert mock_llmobs_span_writer.enqueue.call_count == 1 - mock_llmobs_span_writer.assert_has_calls( - [mock.call.enqueue(_expected_llmobs_llm_span_event(root_llm_span, "llm", tags={"service": ""}))] - ) - - -def test_processor_only_creates_llmobs_span_event(): - """Test that the LLMObsTraceProcessor only creates LLMObs span events for LLM span types.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: - root_span._set_ctx_item(SPAN_KIND, "llm") - with dummy_tracer.trace("child_span") as child_span: - with dummy_tracer.trace("llm_span", span_type=SpanTypes.LLM) as grandchild_span: - grandchild_span._set_ctx_item(SPAN_KIND, "llm") - trace = [root_span, child_span, grandchild_span] - expected_grandchild_llmobs_span = _expected_llmobs_llm_span_event(grandchild_span, "llm") - expected_grandchild_llmobs_span["parent_id"] = str(root_span.span_id) - trace_filter.process_trace(trace) - assert mock_llmobs_span_writer.enqueue.call_count == 2 - mock_llmobs_span_writer.assert_has_calls( - [ - mock.call.enqueue(_expected_llmobs_llm_span_event(root_span, "llm")), - mock.call.enqueue(expected_grandchild_llmobs_span), - ] - ) - - -def test_set_correct_parent_id(): - """Test that the parent_id is set as the span_id of the nearest LLMObs span in the span's ancestor tree.""" - dummy_tracer = DummyTracer() - with dummy_tracer.trace("root"): - with dummy_tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: - pass - assert _get_llmobs_parent_id(llm_span) is None - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: - with dummy_tracer.trace("child_span") as child_span: - with dummy_tracer.trace("llm_span", span_type=SpanTypes.LLM) as grandchild_span: - pass - assert _get_llmobs_parent_id(root_span) is None - assert _get_llmobs_parent_id(child_span) == str(root_span.span_id) - assert _get_llmobs_parent_id(grandchild_span) == str(root_span.span_id) - - -def test_propagate_session_id_from_ancestors(): - """ - Test that session_id is propagated from the nearest LLMObs span in the span's ancestor tree - if no session_id is not set on the span itself. - """ - dummy_tracer = DummyTracer() - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: - root_span._set_ctx_item(SESSION_ID, "test_session_id") - with dummy_tracer.trace("child_span"): - with dummy_tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: - pass - assert _get_session_id(llm_span) == "test_session_id" - - -def test_session_id_if_set_manually(): - """Test that session_id is extracted from the span if it is already set manually.""" - dummy_tracer = DummyTracer() - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as root_span: - root_span._set_ctx_item(SESSION_ID, "test_session_id") - with dummy_tracer.trace("child_span"): - with dummy_tracer.trace("llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SESSION_ID, "test_different_session_id") - assert _get_session_id(llm_span) == "test_different_session_id" - - -def test_session_id_propagates_ignore_non_llmobs_spans(): - """ - Test that when session_id is not set, we propagate from nearest LLMObs ancestor - even if there are non-LLMObs spans in between. - """ - dummy_tracer = DummyTracer() - with override_global_config(dict(_llmobs_ml_app="")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(SESSION_ID, "session-123") - with dummy_tracer.trace("child_span"): - with dummy_tracer.trace("llm_grandchild_span", span_type=SpanTypes.LLM) as grandchild_span: - grandchild_span._set_ctx_item(SPAN_KIND, "llm") - with dummy_tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: - great_grandchild_span._set_ctx_item(SPAN_KIND, "llm") - tp = LLMObsTraceProcessor(dummy_tracer._writer) - llm_span_event, _ = tp._llmobs_span_event(llm_span) - grandchild_span_event, _ = tp._llmobs_span_event(grandchild_span) - great_grandchild_span_event, _ = tp._llmobs_span_event(great_grandchild_span) - assert llm_span_event["session_id"] == "session-123" - assert grandchild_span_event["session_id"] == "session-123" - assert great_grandchild_span_event["session_id"] == "session-123" - - -def test_ml_app_tag_defaults_to_env_var(): - """Test that no ml_app defaults to the environment variable DD_LLMOBS_ML_APP.""" - dummy_tracer = DummyTracer() - with override_global_config(dict(_llmobs_ml_app="")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - pass - tp = LLMObsTraceProcessor(dummy_tracer._writer) - span_event, _ = tp._llmobs_span_event(llm_span) - assert "ml_app:" in span_event["tags"] - - -def test_ml_app_tag_overrides_env_var(): - """Test that when ml_app is set on the span, it overrides the environment variable DD_LLMOBS_ML_APP.""" - dummy_tracer = DummyTracer() - with override_global_config(dict(_llmobs_ml_app="")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(ML_APP, "test-ml-app") - tp = LLMObsTraceProcessor(dummy_tracer._writer) - span_event, _ = tp._llmobs_span_event(llm_span) - assert "ml_app:test-ml-app" in span_event["tags"] - - -def test_ml_app_propagates_ignore_non_llmobs_spans(): - """ - Test that when ml_app is not set, we propagate from nearest LLMObs ancestor - even if there are non-LLMObs spans in between. - """ - dummy_tracer = DummyTracer() - with override_global_config(dict(_llmobs_ml_app="")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(ML_APP, "test-ml-app") - with dummy_tracer.trace("child_span"): - with dummy_tracer.trace("llm_grandchild_span", span_type=SpanTypes.LLM) as grandchild_span: - grandchild_span._set_ctx_item(SPAN_KIND, "llm") - with dummy_tracer.trace("great_grandchild_span", span_type=SpanTypes.LLM) as great_grandchild_span: - great_grandchild_span._set_ctx_item(SPAN_KIND, "llm") - tp = LLMObsTraceProcessor(dummy_tracer._writer) - llm_span_event, _ = tp._llmobs_span_event(llm_span) - grandchild_span_event, _ = tp._llmobs_span_event(grandchild_span) - great_grandchild_span_event, _ = tp._llmobs_span_event(great_grandchild_span) - assert "ml_app:test-ml-app" in llm_span_event["tags"] - assert "ml_app:test-ml-app" in grandchild_span_event["tags"] - assert "ml_app:test-ml-app" in great_grandchild_span_event["tags"] - - -def test_malformed_span_logs_error_instead_of_raising(mock_logs): - """Test that a trying to create a span event from a malformed span will log an error instead of crashing.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - # span does not have SPAN_KIND tag - pass - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - tp.process_trace([llm_span]) - mock_logs.error.assert_called_once_with( - "Error generating LLMObs span event for span %s, likely due to malformed span", llm_span - ) - mock_llmobs_span_writer.enqueue.assert_not_called() - - -def test_model_and_provider_are_set(): - """Test that model and provider are set on the span event if they are present on the LLM-kind span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(MODEL_NAME, "model_name") - llm_span._set_ctx_item(MODEL_PROVIDER, "model_provider") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event, _ = tp._llmobs_span_event(llm_span) - assert span_event["meta"]["model_name"] == "model_name" - assert span_event["meta"]["model_provider"] == "model_provider" - - -def test_model_provider_defaults_to_custom(): - """Test that model provider defaults to "custom" if not provided.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(MODEL_NAME, "model_name") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event, _ = tp._llmobs_span_event(llm_span) - assert span_event["meta"]["model_name"] == "model_name" - assert span_event["meta"]["model_provider"] == "custom" - - -def test_model_not_set_if_not_llm_kind_span(): - """Test that model name and provider not set if non-LLM span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_workflow_span", span_type=SpanTypes.LLM) as span: - span._set_ctx_item(SPAN_KIND, "workflow") - span._set_ctx_item(MODEL_NAME, "model_name") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event, _ = tp._llmobs_span_event(span) - assert "model_name" not in span_event["meta"] - assert "model_provider" not in span_event["meta"] - - -def test_input_messages_are_set(): - """Test that input messages are set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(INPUT_MESSAGES, [{"content": "message", "role": "user"}]) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["messages"] == [ - {"content": "message", "role": "user"} - ] - - -def test_input_value_is_set(): - """Test that input value is set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(INPUT_VALUE, "value") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["value"] == "value" - - -def test_input_parameters_are_set(): - """Test that input parameters are set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(INPUT_PARAMETERS, {"key": "value"}) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["parameters"] == {"key": "value"} - - -def test_output_messages_are_set(): - """Test that output messages are set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(OUTPUT_MESSAGES, [{"content": "message", "role": "user"}]) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["output"]["messages"] == [ - {"content": "message", "role": "user"} - ] - - -def test_output_value_is_set(): - """Test that output value is set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(OUTPUT_VALUE, "value") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["output"]["value"] == "value" - - -def test_prompt_is_set(): - """Test that prompt is set on the span event if they are present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(INPUT_PROMPT, {"variables": {"var1": "var2"}}) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["input"]["prompt"] == {"variables": {"var1": "var2"}} - - -def test_prompt_is_not_set_for_non_llm_spans(): - """Test that prompt is NOT set on the span event if the span is not an LLM span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("task_span", span_type=SpanTypes.LLM) as task_span: - task_span._set_ctx_item(SPAN_KIND, "task") - task_span._set_ctx_item(INPUT_VALUE, "ival") - task_span._set_ctx_item(INPUT_PROMPT, {"variables": {"var1": "var2"}}) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(task_span)[0]["meta"]["input"].get("prompt") is None - - -def test_metadata_is_set(): - """Test that metadata is set on the span event if it is present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(METADATA, {"key": "value"}) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["meta"]["metadata"] == {"key": "value"} - - -def test_metrics_are_set(): - """Test that metadata is set on the span event if it is present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - llm_span._set_ctx_item(METRICS, {"tokens": 100}) - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["metrics"] == {"tokens": 100} - - -def test_langchain_span_name_is_set_to_class_name(): - """Test span names for langchain auto-instrumented spans is set correctly.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with dummy_tracer.trace(LANGCHAIN_APM_SPAN_NAME, resource="expected_name", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - assert tp._llmobs_span_event(llm_span)[0]["name"] == "expected_name" - - -def test_error_is_set(): - """Test that error is set on the span event if it is present on the span.""" - dummy_tracer = DummyTracer() - mock_llmobs_span_writer = mock.MagicMock() - with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): - with pytest.raises(ValueError): - with dummy_tracer.trace("root_llm_span", span_type=SpanTypes.LLM) as llm_span: - llm_span._set_ctx_item(SPAN_KIND, "llm") - raise ValueError("error") - tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) - span_event, _ = tp._llmobs_span_event(llm_span) - assert span_event["meta"]["error.message"] == "error" - assert "ValueError" in span_event["meta"]["error.type"] - assert 'raise ValueError("error")' in span_event["meta"]["error.stack"] From d855c4a28824c15fd3afdbbe89315808efafdf07 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:43:12 -0500 Subject: [PATCH 342/372] fix(profiling): reset all profiling c++ mutexes on fork (#11768) I'm not sure why it took so long to surface this defect, but it turns out that stack v2 can deadlock applications because not all mutices are reset. The repro in #11762 appears to be pretty durable. I need to investigate it a bit more in order to distill it down into a native stress test we can use moving forward. In practice, this patch suppresses the noted behavior in the repro. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Taegyun Kim --- .../profiling/dd_wrapper/include/uploader_builder.hpp | 2 ++ .../datadog/profiling/dd_wrapper/src/code_provenance.cpp | 3 +-- .../datadog/profiling/dd_wrapper/src/ddup_interface.cpp | 1 + .../internal/datadog/profiling/dd_wrapper/src/profile.cpp | 2 +- .../internal/datadog/profiling/dd_wrapper/src/sample.cpp | 1 - .../internal/datadog/profiling/dd_wrapper/src/uploader.cpp | 3 ++- .../datadog/profiling/dd_wrapper/src/uploader_builder.cpp | 7 +++++++ .../internal/datadog/profiling/stack_v2/src/sampler.cpp | 5 +++++ .../datadog/profiling/stack_v2/src/thread_span_links.cpp | 4 +--- .../fix-profiling-native-mutices-62440b5a3d9d6c4b.yaml | 5 +++++ 10 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/fix-profiling-native-mutices-62440b5a3d9d6c4b.yaml diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader_builder.hpp b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader_builder.hpp index 62ee6aad853..7077096c744 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader_builder.hpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/include/uploader_builder.hpp @@ -43,6 +43,8 @@ class UploaderBuilder static void set_output_filename(std::string_view _output_filename); static std::variant build(); + + static void postfork_child(); }; } // namespace Datadog diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/code_provenance.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/code_provenance.cpp index 0a4a49a4ce5..f3147cd2034 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/code_provenance.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/code_provenance.cpp @@ -14,9 +14,8 @@ namespace Datadog { void Datadog::CodeProvenance::postfork_child() { - get_instance().mtx.~mutex(); // Destroy the mutex + // NB placement-new to re-init and leak the mutex because doing anything else is UB new (&get_instance().mtx) std::mutex(); // Recreate the mutex - get_instance().reset(); // Reset the state } void diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp index 5d3ef356c2a..9b52cbcaf6d 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/ddup_interface.cpp @@ -24,6 +24,7 @@ ddup_postfork_child() Datadog::Uploader::postfork_child(); Datadog::SampleManager::postfork_child(); Datadog::CodeProvenance::postfork_child(); + Datadog::UploaderBuilder::postfork_child(); } void diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp index 083ad1a655d..860f9c7cd3e 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/profile.cpp @@ -203,6 +203,6 @@ Datadog::Profile::collect(const ddog_prof_Sample& sample, int64_t endtime_ns) void Datadog::Profile::postfork_child() { - profile_mtx.unlock(); + new (&profile_mtx) std::mutex(); cycle_buffers(); } diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp index 1e7ca1b0217..4483a021803 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/sample.cpp @@ -408,7 +408,6 @@ Datadog::Sample::push_absolute_ns(int64_t _timestamp_ns) return true; } - bool Datadog::Sample::push_monotonic_ns(int64_t _monotonic_ns) { diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp index 375c2e09e9e..325771946d8 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader.cpp @@ -175,5 +175,6 @@ Datadog::Uploader::postfork_parent() void Datadog::Uploader::postfork_child() { - unlock(); + // NB placement-new to re-init and leak the mutex because doing anything else is UB + new (&upload_lock) std::mutex(); } diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader_builder.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader_builder.cpp index 0661b7f217f..8ff5d45e7c2 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader_builder.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/uploader_builder.cpp @@ -186,3 +186,10 @@ Datadog::UploaderBuilder::build() return Datadog::Uploader{ output_filename, ddog_exporter }; } + +void +Datadog::UploaderBuilder::postfork_child() +{ + // NB placement-new to re-init and leak the mutex because doing anything else is UB + new (&tag_mutex) std::mutex(); +} diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp index c05ae45477e..7ad9ad692b2 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/sampler.cpp @@ -67,6 +67,11 @@ _stack_v2_atfork_child() // so we don't even reveal this function to the user _set_pid(getpid()); ThreadSpanLinks::postfork_child(); + + // `thread_info_map_lock` and `task_link_map_lock` are global locks held in echion + // NB placement-new to re-init and leak the mutex because doing anything else is UB + new (&thread_info_map_lock) std::mutex; + new (&task_link_map_lock) std::mutex; } __attribute__((constructor)) void diff --git a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp index c777ff8a510..6be43a04a42 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp +++ b/ddtrace/internal/datadog/profiling/stack_v2/src/thread_span_links.cpp @@ -53,10 +53,8 @@ ThreadSpanLinks::reset() void ThreadSpanLinks::postfork_child() { - // Explicitly destroy and reconstruct the mutex to avoid undefined behavior - get_instance().mtx.~mutex(); + // NB placement-new to re-init and leak the mutex because doing anything else is UB new (&get_instance().mtx) std::mutex(); - get_instance().reset(); } diff --git a/releasenotes/notes/fix-profiling-native-mutices-62440b5a3d9d6c4b.yaml b/releasenotes/notes/fix-profiling-native-mutices-62440b5a3d9d6c4b.yaml new file mode 100644 index 00000000000..40167a974c3 --- /dev/null +++ b/releasenotes/notes/fix-profiling-native-mutices-62440b5a3d9d6c4b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: Fixes a bug where profiling mutexes were not cleared on fork in the child process. This could + cause deadlocks in certain configurations. From 983c84f5f0981e57a4e9125d2d3ab2367d0df8b5 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:44:31 -0500 Subject: [PATCH 343/372] fix(profiler): update memalloc guard (#11460) Previously, the memory allocation profiler would use Python's builtin thread-local storage interfaces in order to set and get the state of a thread-local guard. I've updated a few things here. * I think get/set idioms are slightly problematic for this type of code, since it pushes the responsibility of maintaining clean internal state up to the parent. A consequence of this is that the propagation of the underlying state _by value_ opens the door for race conditions if execution changes between contexts (unlikely here, but I think minimizing indirection is still cleaner). Accordingly, I've updated this to use native thread-local storage * Based on @nsrip-dd's observation, I widened the guard over `free()` operations. I believe this is correct, and if it isn't then the detriment is performance, not correctness. * I got rid of the PY37 failovers We don't have any reproductions for the defects that prompted this change, but I've been running a patched library in an environment that _does_ reproduce the behavior, and I haven't seen any defects. 1. I don't believe this patch is harmful, and if our memory allocation tests pass then I believe it should be fine. 2. I have a reason to believe this fixes a critical defect, which can cause crashes. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/profiling/collector/_memalloc.c | 125 ++++++++---- ddtrace/profiling/collector/_memalloc_heap.c | 93 +++++++-- .../profiling/collector/_memalloc_reentrant.c | 3 + .../profiling/collector/_memalloc_reentrant.h | 186 +++++++++++++++--- ddtrace/profiling/collector/_memalloc_tb.c | 7 +- ddtrace/profiling/collector/_pymacro.h | 4 - ...ng-memalloc-segfault-5593ad951405a75d.yaml | 5 + setup.py | 5 +- 8 files changed, 340 insertions(+), 88 deletions(-) create mode 100644 ddtrace/profiling/collector/_memalloc_reentrant.c create mode 100644 releasenotes/notes/fix-profiling-memalloc-segfault-5593ad951405a75d.yaml diff --git a/ddtrace/profiling/collector/_memalloc.c b/ddtrace/profiling/collector/_memalloc.c index 3b7f7db293f..3876517baaf 100644 --- a/ddtrace/profiling/collector/_memalloc.c +++ b/ddtrace/profiling/collector/_memalloc.c @@ -42,47 +42,95 @@ static PyObject* object_string = NULL; #define ALLOC_TRACKER_MAX_COUNT UINT64_MAX +// The data coordination primitives in this and related files are related to a crash we started seeing. +// We don't have a precise understanding of the causal factors within the runtime that lead to this condition, +// since the GIL alone was sufficient in the past for preventing this issue. +// We add an option here to _add_ a crash, in order to observe this condition in a future diagnostic iteration. +// **This option is _intended_ to crash the Python process** do not use without a good reason! +static char g_crash_on_mutex_pass_str[] = "_DD_PROFILING_MEMALLOC_CRASH_ON_MUTEX_PASS"; +static const char* g_truthy_values[] = { "1", "true", "yes", "on", "enable", "enabled", NULL }; // NB the sentinel NULL +static memlock_t g_memalloc_lock; + static alloc_tracker_t* global_alloc_tracker; +// This is a multiplatform way to define an operation to happen at static initialization time +static void +memalloc_init(void); + +#ifdef _MSC_VER +#pragma section(".CRT$XCU", read) +__declspec(allocate(".CRT$XCU")) void (*memalloc_init_func)(void) = memalloc_init; + +#elif defined(__GNUC__) || defined(__clang__) +__attribute__((constructor)) +#else +#error Unsupported compiler +#endif +static void +memalloc_init() +{ + // Check if we should crash the process on mutex pass + char* crash_on_mutex_pass_str = getenv(g_crash_on_mutex_pass_str); + bool crash_on_mutex_pass = false; + if (crash_on_mutex_pass_str) { + for (int i = 0; g_truthy_values[i]; i++) { + if (strcmp(crash_on_mutex_pass_str, g_truthy_values[i]) == 0) { + crash_on_mutex_pass = true; + break; + } + } + } + memlock_init(&g_memalloc_lock, crash_on_mutex_pass); +} + static void memalloc_add_event(memalloc_context_t* ctx, void* ptr, size_t size) { - /* Do not overflow; just ignore the new events if we ever reach that point */ - if (global_alloc_tracker->alloc_count >= ALLOC_TRACKER_MAX_COUNT) + uint64_t alloc_count = atomic_add_clamped(&global_alloc_tracker->alloc_count, 1, ALLOC_TRACKER_MAX_COUNT); + + /* Return if we've reached the maximum number of allocations */ + if (alloc_count == 0) return; - global_alloc_tracker->alloc_count++; + // Return if we can't take the guard + if (!memalloc_take_guard()) { + return; + } - /* Avoid loops */ - if (memalloc_get_reentrant()) + // In this implementation, the `global_alloc_tracker` isn't intrinsically protected. Before we read or modify, + // take the lock. The count of allocations is already forward-attributed elsewhere, so if we can't take the lock + // there's nothing to do. + if (!memlock_trylock(&g_memalloc_lock)) { return; + } /* Determine if we can capture or if we need to sample */ if (global_alloc_tracker->allocs.count < ctx->max_events) { - /* set a barrier so we don't loop as getting a traceback allocates memory */ - memalloc_set_reentrant(true); /* Buffer is not full, fill it */ traceback_t* tb = memalloc_get_traceback(ctx->max_nframe, ptr, size, ctx->domain); - memalloc_set_reentrant(false); - if (tb) + if (tb) { traceback_array_append(&global_alloc_tracker->allocs, tb); + } } else { /* Sampling mode using a reservoir sampling algorithm: replace a random * traceback with this one */ - uint64_t r = random_range(global_alloc_tracker->alloc_count); + uint64_t r = random_range(alloc_count); - if (r < ctx->max_events) { - /* set a barrier so we don't loop as getting a traceback allocates memory */ - memalloc_set_reentrant(true); + // In addition to event size, need to check that the tab is in a good state + if (r < ctx->max_events && global_alloc_tracker->allocs.tab != NULL) { /* Replace a random traceback with this one */ traceback_t* tb = memalloc_get_traceback(ctx->max_nframe, ptr, size, ctx->domain); - memalloc_set_reentrant(false); + + // Need to check not only that the tb returned if (tb) { traceback_free(global_alloc_tracker->allocs.tab[r]); global_alloc_tracker->allocs.tab[r] = tb; } } } + + memlock_unlock(&g_memalloc_lock); + memalloc_yield_guard(); } static void @@ -98,12 +146,6 @@ memalloc_free(void* ctx, void* ptr) alloc->free(alloc->ctx, ptr); } -#ifdef _PY37_AND_LATER -Py_tss_t memalloc_reentrant_key = Py_tss_NEEDS_INIT; -#else -int memalloc_reentrant_key = -1; -#endif - static void* memalloc_alloc(int use_calloc, void* ctx, size_t nelem, size_t elsize) { @@ -233,7 +275,10 @@ memalloc_start(PyObject* Py_UNUSED(module), PyObject* args) global_memalloc_ctx.domain = PYMEM_DOMAIN_OBJ; - global_alloc_tracker = alloc_tracker_new(); + if (memlock_trylock(&g_memalloc_lock)) { + global_alloc_tracker = alloc_tracker_new(); + memlock_unlock(&g_memalloc_lock); + } PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &global_memalloc_ctx.pymem_allocator_obj); PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc); @@ -258,8 +303,11 @@ memalloc_stop(PyObject* Py_UNUSED(module), PyObject* Py_UNUSED(args)) PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &global_memalloc_ctx.pymem_allocator_obj); memalloc_tb_deinit(); - alloc_tracker_free(global_alloc_tracker); - global_alloc_tracker = NULL; + if (memlock_trylock(&g_memalloc_lock)) { + alloc_tracker_free(global_alloc_tracker); + global_alloc_tracker = NULL; + memlock_unlock(&g_memalloc_lock); + } memalloc_heap_tracker_deinit(); @@ -310,9 +358,15 @@ iterevents_new(PyTypeObject* type, PyObject* Py_UNUSED(args), PyObject* Py_UNUSE if (!iestate) return NULL; - iestate->alloc_tracker = global_alloc_tracker; /* reset the current traceback list */ - global_alloc_tracker = alloc_tracker_new(); + if (memlock_trylock(&g_memalloc_lock)) { + iestate->alloc_tracker = global_alloc_tracker; + global_alloc_tracker = alloc_tracker_new(); + memlock_unlock(&g_memalloc_lock); + } else { + Py_TYPE(iestate)->tp_free(iestate); + return NULL; + } iestate->seq_index = 0; PyObject* iter_and_count = PyTuple_New(3); @@ -326,8 +380,11 @@ iterevents_new(PyTypeObject* type, PyObject* Py_UNUSED(args), PyObject* Py_UNUSE static void iterevents_dealloc(IterEventsState* iestate) { - alloc_tracker_free(iestate->alloc_tracker); - Py_TYPE(iestate)->tp_free(iestate); + if (memlock_trylock(&g_memalloc_lock)) { + alloc_tracker_free(iestate->alloc_tracker); + Py_TYPE(iestate)->tp_free(iestate); + memlock_unlock(&g_memalloc_lock); + } } static PyObject* @@ -442,20 +499,6 @@ PyInit__memalloc(void) return NULL; } -#ifdef _PY37_AND_LATER - if (PyThread_tss_create(&memalloc_reentrant_key) != 0) { -#else - memalloc_reentrant_key = PyThread_create_key(); - if (memalloc_reentrant_key == -1) { -#endif -#ifdef MS_WINDOWS - PyErr_SetFromWindowsErr(0); -#else - PyErr_SetFromErrno(PyExc_OSError); -#endif - return NULL; - } - if (PyType_Ready(&MemallocIterEvents_Type) < 0) return NULL; Py_INCREF((PyObject*)&MemallocIterEvents_Type); diff --git a/ddtrace/profiling/collector/_memalloc_heap.c b/ddtrace/profiling/collector/_memalloc_heap.c index d6531d7b095..d2a5cc29eee 100644 --- a/ddtrace/profiling/collector/_memalloc_heap.c +++ b/ddtrace/profiling/collector/_memalloc_heap.c @@ -9,13 +9,13 @@ typedef struct { /* Granularity of the heap profiler in bytes */ - uint32_t sample_size; + uint64_t sample_size; /* Current sample size of the heap profiler in bytes */ - uint32_t current_sample_size; + uint64_t current_sample_size; /* Tracked allocations */ traceback_array_t allocs; /* Allocated memory counter in bytes */ - uint32_t allocated_memory; + uint64_t allocated_memory; /* True if the heap tracker is frozen */ bool frozen; /* Contains the ongoing heap allocation/deallocation while frozen */ @@ -26,8 +26,42 @@ typedef struct } freezer; } heap_tracker_t; +static char g_crash_on_mutex_pass_str[] = "_DD_PROFILING_MEMHEAP_CRASH_ON_MUTEX_PASS"; +static const char* g_truthy_values[] = { "1", "true", "yes", "on", "enable", "enabled", NULL }; // NB the sentinel NULL +static memlock_t g_memheap_lock; + static heap_tracker_t global_heap_tracker; +// This is a multiplatform way to define an operation to happen at static initialization time +static void +memheap_init(void); + +#ifdef _MSC_VER +#pragma section(".CRT$XCU", read) +__declspec(allocate(".CRT$XCU")) void (*memheap_init_func)(void) = memheap_init; + +#elif defined(__GNUC__) || defined(__clang__) +__attribute__((constructor)) +#else +#error Unsupported compiler +#endif +static void +memheap_init() +{ + // Check if we should crash the process on mutex pass + char* crash_on_mutex_pass_str = getenv(g_crash_on_mutex_pass_str); + bool crash_on_mutex_pass = false; + if (crash_on_mutex_pass_str) { + for (int i = 0; g_truthy_values[i]; i++) { + if (strcmp(crash_on_mutex_pass_str, g_truthy_values[i]) == 0) { + crash_on_mutex_pass = true; + break; + } + } + } + memlock_init(&g_memheap_lock, crash_on_mutex_pass); +} + static uint32_t heap_tracker_next_sample_size(uint32_t sample_size) { @@ -119,20 +153,30 @@ heap_tracker_thaw(heap_tracker_t* heap_tracker) void memalloc_heap_tracker_init(uint32_t sample_size) { - heap_tracker_init(&global_heap_tracker); - global_heap_tracker.sample_size = sample_size; - global_heap_tracker.current_sample_size = heap_tracker_next_sample_size(sample_size); + + if (memlock_trylock(&g_memheap_lock)) { + heap_tracker_init(&global_heap_tracker); + global_heap_tracker.sample_size = sample_size; + global_heap_tracker.current_sample_size = heap_tracker_next_sample_size(sample_size); + memlock_unlock(&g_memheap_lock); + } } void memalloc_heap_tracker_deinit(void) { - heap_tracker_wipe(&global_heap_tracker); + if (memlock_trylock(&g_memheap_lock)) { + heap_tracker_wipe(&global_heap_tracker); + memlock_unlock(&g_memheap_lock); + } } void memalloc_heap_untrack(void* ptr) { + if (!memlock_trylock(&g_memheap_lock)) { + return; + } if (global_heap_tracker.frozen) { /* Check that we still have space to store the free. If we don't have enough space, we ignore the untrack. That's sad as there is a change @@ -144,6 +188,8 @@ memalloc_heap_untrack(void* ptr) ptr_array_append(&global_heap_tracker.freezer.frees, ptr); } else heap_tracker_untrack_thawed(&global_heap_tracker, ptr); + + memlock_unlock(&g_memheap_lock); } /* Track a memory allocation in the heap profiler. @@ -157,26 +203,36 @@ memalloc_heap_track(uint16_t max_nframe, void* ptr, size_t size, PyMemAllocatorD return false; /* Check for overflow */ - global_heap_tracker.allocated_memory = Py_MIN(global_heap_tracker.allocated_memory + size, MAX_HEAP_SAMPLE_SIZE); + uint64_t res = atomic_add_clamped(&global_heap_tracker.allocated_memory, size, MAX_HEAP_SAMPLE_SIZE); + if (0 == res) + return false; + + // Take the lock + if (!memlock_trylock(&g_memheap_lock)) { + return false; + } /* Check if we have enough sample or not */ - if (global_heap_tracker.allocated_memory < global_heap_tracker.current_sample_size) + if (global_heap_tracker.allocated_memory < global_heap_tracker.current_sample_size) { + memlock_unlock(&g_memheap_lock); return false; + } /* Check if we can add more samples: the sum of the freezer + alloc tracker cannot be greater than what the alloc tracker can handle: when the alloc tracker is thawed, all the allocs in the freezer will be moved there!*/ - if ((global_heap_tracker.freezer.allocs.count + global_heap_tracker.allocs.count) >= TRACEBACK_ARRAY_MAX_COUNT) + if (global_heap_tracker.freezer.allocs.count + global_heap_tracker.allocs.count >= TRACEBACK_ARRAY_MAX_COUNT) { + memlock_unlock(&g_memheap_lock); return false; + } /* Avoid loops */ - if (memalloc_get_reentrant()) + if (!memalloc_take_guard()) { + memlock_unlock(&g_memheap_lock); return false; + } - memalloc_set_reentrant(true); traceback_t* tb = memalloc_get_traceback(max_nframe, ptr, global_heap_tracker.allocated_memory, domain); - memalloc_set_reentrant(false); - if (tb) { if (global_heap_tracker.frozen) traceback_array_append(&global_heap_tracker.freezer.allocs, tb); @@ -189,15 +245,23 @@ memalloc_heap_track(uint16_t max_nframe, void* ptr, size_t size, PyMemAllocatorD /* Compute the new target sample size */ global_heap_tracker.current_sample_size = heap_tracker_next_sample_size(global_heap_tracker.sample_size); + memalloc_yield_guard(); + memlock_unlock(&g_memheap_lock); return true; } + memalloc_yield_guard(); + memlock_unlock(&g_memheap_lock); return false; } PyObject* memalloc_heap() { + if (!memlock_trylock(&g_memheap_lock)) { + return NULL; + } + heap_tracker_freeze(&global_heap_tracker); PyObject* heap_list = PyList_New(global_heap_tracker.allocs.count); @@ -213,5 +277,6 @@ memalloc_heap() heap_tracker_thaw(&global_heap_tracker); + memlock_unlock(&g_memheap_lock); return heap_list; } diff --git a/ddtrace/profiling/collector/_memalloc_reentrant.c b/ddtrace/profiling/collector/_memalloc_reentrant.c new file mode 100644 index 00000000000..d360d19fb30 --- /dev/null +++ b/ddtrace/profiling/collector/_memalloc_reentrant.c @@ -0,0 +1,3 @@ +#include "_memalloc_reentrant.h" + +bool _MEMALLOC_ON_THREAD = false; diff --git a/ddtrace/profiling/collector/_memalloc_reentrant.h b/ddtrace/profiling/collector/_memalloc_reentrant.h index 5c8a552294e..cb4aa246961 100644 --- a/ddtrace/profiling/collector/_memalloc_reentrant.h +++ b/ddtrace/profiling/collector/_memalloc_reentrant.h @@ -1,50 +1,188 @@ #ifndef _DDTRACE_MEMALLOC_REENTRANT_H #define _DDTRACE_MEMALLOC_REENTRANT_H -#include "_pymacro.h" +#ifdef _WIN32 +#include +#else +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#endif #include +#include +#include -#ifndef _PY37_AND_LATER -#include +// Cross-platform macro for defining thread-local storage +// NB - we use dynamic-global on Linux because the others are problematic +#if defined(_MSC_VER) // Check for MSVC compiler +#define MEMALLOC_TLS __declspec(thread) +#elif defined(__GNUC__) || defined(__clang__) // GCC or Clang +#define MEMALLOC_TLS __attribute__((tls_model("global-dynamic"))) __thread +#else +#error "Unsupported compiler for thread-local storage" #endif +extern bool _MEMALLOC_ON_THREAD; + +// This is a saturating atomic add for 32- and 64-bit platforms. +// In order to implement the saturation logic, use a CAS loop. +// From the GCC docs: +// "‘__atomic’ builtins can be used with any integral scalar or pointer type that is 1, 2, 4, or 8 bytes in length" +// From the MSVC docs: +// "_InterlockedCompareExchange64 is available on x86 systems running on any Pentium architecture; it is not +// available on 386 or 486 architectures." +static inline uint64_t +atomic_add_clamped(uint64_t* target, uint64_t amount, uint64_t max) +{ + // In reality, there's virtually no scenario in which this deadlocks. Just the same, give it some arbitrarily high + // limit in order to prevent unpredicted deadlocks. 96 is chosen since it's the number of cores on the largest + // consumer CPU generally used by our customers. + int attempts = 96; + while (attempts--) { + uint64_t old_val = (volatile uint64_t) * target; -#ifdef _PY37_AND_LATER -extern Py_tss_t memalloc_reentrant_key; + // CAS loop + saturation check + uint64_t new_val = old_val + amount; + if (new_val > max || new_val < old_val) { + return 0; + } +#if defined(_MSC_VER) + uint64_t prev_val = + (uint64_t)InterlockedCompareExchange64((volatile LONG64*)target, (LONG64)new_val, (LONG64)old_val); + if (prev_val == old_val) { + return new_val; + } +#elif defined(__clang__) || defined(__GNUC__) + if (atomic_compare_exchange_strong_explicit( + (_Atomic uint64_t*)target, &old_val, new_val, memory_order_seq_cst, memory_order_seq_cst)) { + return new_val; + } #else -extern int memalloc_reentrant_key; +#error "Unsupported compiler for atomic operations" #endif + // If we reach here, CAS failed; another thread changed `target` + // Retry until success or until we detect max. + } -/* Any non-NULL pointer can be used */ -#define _MEMALLOC_REENTRANT_VALUE Py_True + return 0; +} -static inline void -memalloc_set_reentrant(bool reentrant) +// Opaque lock type +typedef struct +{ +#ifdef _WIN32 + HANDLE mutex; +#else + pthread_mutex_t mutex; +#endif +} memlock_t; + +// Global setting; if a lock fails to be acquired, crash +static bool g_crash_on_mutex_pass = false; + +// Generic initializer +static inline bool +memlock_init(memlock_t* lock, bool crash_on_pass) +{ + if (!lock) + return false; + + g_crash_on_mutex_pass = crash_on_pass; + +#ifdef _WIN32 + lock->mutex = CreateMutex(NULL, FALSE, NULL); + return lock->mutex != NULL; +#else + // For POSIX systems, we make sure to use an ERRORCHECK type mutex, since it pushes some of the state checking + // down to the implementation. + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + return pthread_mutex_init(&lock->mutex, NULL) == 0; +#endif +} + +// Unlock function +static inline bool +memlock_unlock(memlock_t* lock) { - if (reentrant) -#ifdef _PY37_AND_LATER - PyThread_tss_set(&memalloc_reentrant_key, _MEMALLOC_REENTRANT_VALUE); + if (!lock) + return false; + +#ifdef _WIN32 + return ReleaseMutex(lock->mutex); #else - PyThread_set_key_value(memalloc_reentrant_key, _MEMALLOC_REENTRANT_VALUE); + return pthread_mutex_unlock(&lock->mutex) == 0; +#endif +} + +// trylock function +static inline bool +memlock_trylock(memlock_t* lock) +{ + if (!lock) + return false; + +#ifdef __linux__ + // On Linux, we need to make sure we didn't just fork + // pthreads will guarantee the lock is consistent, but we at least need to clear it + static pid_t my_pid = 0; + if (my_pid == 0) { + my_pid = getpid(); + } else if (my_pid != getpid()) { + // We've forked, so we need to free the lock + memlock_unlock(lock); + my_pid = getpid(); + } #endif - else -#ifdef _PY37_AND_LATER - PyThread_tss_set(&memalloc_reentrant_key, NULL); + +#ifdef _WIN32 + bool result = WAIT_OBJECT_0 == WaitForSingleObject(lock->mutex, 0); // 0ms timeout -> no wait #else - PyThread_set_key_value(memalloc_reentrant_key, NULL); + bool result = 0 == pthread_mutex_trylock(&lock->mutex); #endif + if (!result && g_crash_on_mutex_pass) { + // segfault + int* p = NULL; + *p = 0; + abort(); // should never reach here + } + + return result; } +// Cleanup function static inline bool -memalloc_get_reentrant(void) +memlock_destroy(memlock_t* lock) { -#ifdef _PY37_AND_LATER - if (PyThread_tss_get(&memalloc_reentrant_key)) + if (!lock) + return false; + +#ifdef _WIN32 + return CloseHandle(lock->mutex); #else - if (PyThread_get_key_value(memalloc_reentrant_key)) + return 0 == pthread_mutex_destroy(&lock->mutex); #endif - return true; +} - return false; +static inline bool +memalloc_take_guard() +{ + // Ordinarilly, a process-wide semaphore would require a CAS, but since this is thread-local we can just set it. + if (_MEMALLOC_ON_THREAD) + return false; + _MEMALLOC_ON_THREAD = true; + return true; +} + +static inline void +memalloc_yield_guard(void) +{ + // Ideally, we'd actually capture the old state within an object and restore it, but since this is + // a coarse-grained lock, we just set it to false. + _MEMALLOC_ON_THREAD = false; } #endif diff --git a/ddtrace/profiling/collector/_memalloc_tb.c b/ddtrace/profiling/collector/_memalloc_tb.c index ba79021f719..bb265fe08d5 100644 --- a/ddtrace/profiling/collector/_memalloc_tb.c +++ b/ddtrace/profiling/collector/_memalloc_tb.c @@ -87,6 +87,9 @@ memalloc_tb_deinit(void) void traceback_free(traceback_t* tb) { + if (!tb) + return; + for (uint16_t nframe = 0; nframe < tb->nframe; nframe++) { Py_DECREF(tb->frames[nframe].filename); Py_DECREF(tb->frames[nframe].name); @@ -197,11 +200,7 @@ memalloc_get_traceback(uint16_t max_nframe, void* ptr, size_t size, PyMemAllocat traceback->size = size; traceback->ptr = ptr; -#ifdef _PY37_AND_LATER traceback->thread_id = PyThread_get_thread_ident(); -#else - traceback->thread_id = tstate->thread_id; -#endif traceback->domain = domain; diff --git a/ddtrace/profiling/collector/_pymacro.h b/ddtrace/profiling/collector/_pymacro.h index e71ed6888b9..aa31c3d4cc1 100644 --- a/ddtrace/profiling/collector/_pymacro.h +++ b/ddtrace/profiling/collector/_pymacro.h @@ -13,8 +13,4 @@ #define _PY38 #endif -#if PY_VERSION_HEX >= 0x03070000 -#define _PY37_AND_LATER -#endif - #endif diff --git a/releasenotes/notes/fix-profiling-memalloc-segfault-5593ad951405a75d.yaml b/releasenotes/notes/fix-profiling-memalloc-segfault-5593ad951405a75d.yaml new file mode 100644 index 00000000000..8632b62af50 --- /dev/null +++ b/releasenotes/notes/fix-profiling-memalloc-segfault-5593ad951405a75d.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes an issue where the memory allocation profiler can cause a segmentation fault due to + data races when accessing its own global data structures from multiple threads. diff --git a/setup.py b/setup.py index 74e8f8187d7..dfaa5f6bf97 100644 --- a/setup.py +++ b/setup.py @@ -510,8 +510,11 @@ def get_exts_for(name): "ddtrace/profiling/collector/_memalloc.c", "ddtrace/profiling/collector/_memalloc_tb.c", "ddtrace/profiling/collector/_memalloc_heap.c", + "ddtrace/profiling/collector/_memalloc_reentrant.c", ], - extra_compile_args=debug_compile_args, + extra_compile_args=debug_compile_args + ["-D_POSIX_C_SOURCE=200809L", "-std=c11"] + if CURRENT_OS != "Windows" + else ["/std:c11"], ), Extension( "ddtrace.internal._threads", From e8aab659df2df0769586856bdf9f3eaefcfbbb5b Mon Sep 17 00:00:00 2001 From: wantsui Date: Thu, 19 Dec 2024 15:06:32 -0500 Subject: [PATCH 344/372] fix(celery): stop closing prerun_span too soon to account for Celery chains scenario (#11498) We've made a few changes to handle celery context recently, including: https://github.com/DataDog/dd-trace-py/pull/10676 In particular the goal of https://github.com/DataDog/dd-trace-py/pull/10676 was to handle a scenario where a long running task may run into an exception, preventing it from closing. Unfortunately, this scenario did not account for cases where tasks are chained and may not close until later. See: https://github.com/DataDog/dd-trace-py/issues/11479 and https://github.com/DataDog/dd-trace-py/issues/11624 With this PR, the sample app in https://github.com/DataDog/dd-trace-py/issues/11479 would attach the celery specific span back to the root span. I also need to add tests for the chains scenario. Related to AIDM-494 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/celery/app.py | 11 ---- ddtrace/contrib/internal/celery/signals.py | 3 - ...-celery-closed-spans-34ff43868c1e33b8.yaml | 4 ++ tests/contrib/celery/run_tasks.py | 5 ++ tests/contrib/celery/tasks.py | 14 +++++ tests/contrib/celery/test_chained_task.py | 62 +++++++++++++++++++ 6 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/fix-celery-closed-spans-34ff43868c1e33b8.yaml create mode 100644 tests/contrib/celery/run_tasks.py create mode 100644 tests/contrib/celery/tasks.py create mode 100644 tests/contrib/celery/test_chained_task.py diff --git a/ddtrace/contrib/internal/celery/app.py b/ddtrace/contrib/internal/celery/app.py index b61585097a7..42eed2cb468 100644 --- a/ddtrace/contrib/internal/celery/app.py +++ b/ddtrace/contrib/internal/celery/app.py @@ -133,10 +133,6 @@ def _traced_apply_async_inner(func, instance, args, kwargs): if task_span: task_span.set_exc_info(*sys.exc_info()) - prerun_span = core.get_item("prerun_span") - if prerun_span: - prerun_span.set_exc_info(*sys.exc_info()) - raise finally: task_span = core.get_item("task_span") @@ -147,11 +143,4 @@ def _traced_apply_async_inner(func, instance, args, kwargs): ) task_span.finish() - prerun_span = core.get_item("prerun_span") - if prerun_span: - log.debug( - "The task_postrun signal was not called, so manually closing span: %s", prerun_span._pprint() - ) - prerun_span.finish() - return _traced_apply_async_inner diff --git a/ddtrace/contrib/internal/celery/signals.py b/ddtrace/contrib/internal/celery/signals.py index 76f07ee7524..8f27fcc53b0 100644 --- a/ddtrace/contrib/internal/celery/signals.py +++ b/ddtrace/contrib/internal/celery/signals.py @@ -54,9 +54,6 @@ def trace_prerun(*args, **kwargs): service = config.celery["worker_service_name"] span = pin.tracer.trace(c.WORKER_ROOT_SPAN, service=service, resource=task.name, span_type=SpanTypes.WORKER) - # Store an item called "prerun span" in case task_postrun doesn't get called - core.set_item("prerun_span", span) - # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) diff --git a/releasenotes/notes/fix-celery-closed-spans-34ff43868c1e33b8.yaml b/releasenotes/notes/fix-celery-closed-spans-34ff43868c1e33b8.yaml new file mode 100644 index 00000000000..f16f7b36fed --- /dev/null +++ b/releasenotes/notes/fix-celery-closed-spans-34ff43868c1e33b8.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + tracing(celery): Fixes an issue where ``celery.apply`` spans from Celery prerun got closed too soon leading to span tags being missing. \ No newline at end of file diff --git a/tests/contrib/celery/run_tasks.py b/tests/contrib/celery/run_tasks.py new file mode 100644 index 00000000000..e91454ab5bb --- /dev/null +++ b/tests/contrib/celery/run_tasks.py @@ -0,0 +1,5 @@ +from tasks import fn_a +from tasks import fn_b + + +(fn_a.si() | fn_b.si()).delay() diff --git a/tests/contrib/celery/tasks.py b/tests/contrib/celery/tasks.py new file mode 100644 index 00000000000..a9dfc936ae4 --- /dev/null +++ b/tests/contrib/celery/tasks.py @@ -0,0 +1,14 @@ +from celery import Celery + + +app = Celery("tasks") + + +@app.task(name="tests.contrib.celery.tasks.fn_a") +def fn_a(): + return "a" + + +@app.task(name="tests.contrib.celery.tasks.fn_b") +def fn_b(): + return "b" diff --git a/tests/contrib/celery/test_chained_task.py b/tests/contrib/celery/test_chained_task.py new file mode 100644 index 00000000000..5fd0c543e72 --- /dev/null +++ b/tests/contrib/celery/test_chained_task.py @@ -0,0 +1,62 @@ +import os +import re +import subprocess +import time + +from celery import Celery + + +# Ensure that when we call Celery chains, the root span has celery specific span tags +# The test_integration.py setup doesn't perfectly mimic the condition of a worker process running. +# This test runs the worker as a side so we can check the tracer logs afterwards to ensure expected span results. +# See https://github.com/DataDog/dd-trace-py/issues/11479 +def test_task_chain_task_call_task(): + app = Celery("tasks") + + celery_worker_cmd = "ddtrace-run celery -A tasks worker -c 1 -l DEBUG -n uniquename1 -P solo" + celery_task_runner_cmd = "ddtrace-run python run_tasks.py" + + # The commands need to run from the directory where this test file lives + current_directory = str(os.path.dirname(__file__)) + + worker_process = subprocess.Popen( + celery_worker_cmd.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=os.setsid, + close_fds=True, + cwd=current_directory, + ) + + max_wait_time = 10 + waited_so_far = 0 + # {app.control.inspect().active() returns {'celery@uniquename1': []} when the worker is running} + while app.control.inspect().active() is None and waited_so_far < max_wait_time: + time.sleep(1) + waited_so_far += 1 + + # The task should only run after the Celery worker has sufficient time to start up + task_runner_process = subprocess.Popen( + celery_task_runner_cmd.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=os.setsid, + close_fds=True, + cwd=current_directory, + ) + + task_runner_process.wait() + # Kill the process so it starts to send traces to the Trace Agent + worker_process.kill() + worker_logs = worker_process.stderr.read() + + # Check that the root span was created with one of the Celery specific tags, such as celery.correlation_id + # Some versions of python seem to require escaping when using `re.search`: + old_pattern_match = r"resource=\\'tests.contrib.celery.tasks.fn_a\\' type=\\'worker\\' .* tags=.*correlation_id.*" + new_pattern_match = r"resource=\'tests.contrib.celery.tasks.fn_a\' type=\'worker\' .* tags=.*correlation_id.*" + + pattern_exists = ( + re.search(old_pattern_match, str(worker_logs)) is not None + or re.search(new_pattern_match, str(worker_logs)) is not None + ) + assert pattern_exists is not None From 3b4bd62c81651baf2c8c3af398295982a9a0ecf4 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Thu, 19 Dec 2024 15:21:18 -0500 Subject: [PATCH 345/372] chore: output supported versions of integrations (#11372) - Creates a `Generate Supported Integration Versions` workflow that outputs the supported versions of integrations to a `supported_versions_output.json` and `supported_versions_table.csv`. PR here: https://github.com/DataDog/dd-trace-py/pull/11767 and workflow here: https://github.com/DataDog/dd-trace-py/actions/runs/12383562860/job/34566489841 - in `scripts/freshvenvs.py`, separates the workflows for outputting the outdated integrations (which is run in the `Generate Package Versions` workflow), and for creating the supported version table. - This workflow will be tied to a release, but can also be triggered manually (via `workflow_dispatch`) Future: - There will be a mechanism for converting the `csv` file to the `rst` format used by the ddtrace docs, and for generating the public datadoghq docs (in markdown) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../workflows/generate-supported-versions.yml | 121 +++++++++++++++++ scripts/freshvenvs.py | 128 ++++++++++++++++-- scripts/generate_table.py | 24 ++++ scripts/regenerate-riot-latest.sh | 5 +- 4 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/generate-supported-versions.yml create mode 100644 scripts/generate_table.py diff --git a/.github/workflows/generate-supported-versions.yml b/.github/workflows/generate-supported-versions.yml new file mode 100644 index 00000000000..c802e91bcf3 --- /dev/null +++ b/.github/workflows/generate-supported-versions.yml @@ -0,0 +1,121 @@ +name: Generate Supported Integration Versions + +on: + workflow_dispatch: # can be triggered manually + +jobs: + generate-supported-versions: + name: Generate supported integration versions + runs-on: ubuntu-22.04 + permissions: + actions: read + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Python 3.7 + uses: actions/setup-python@v5 + with: + python-version: "3.7" + + - name: Setup Python 3.8 + uses: actions/setup-python@v5 + with: + python-version: "3.8" + + - name: Setup Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: "3.9" + + - name: Setup Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Setup Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Setup Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libmariadb-dev + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install packaging + pip install requests + pip install riot==0.20.1 + pip install wrapt==1.16.0 + + - name: Install ddtrace + run: | + pip install -e . + + - run: python scripts/freshvenvs.py generate + + - name: Generate table + run: python scripts/generate_table.py + + - run: git diff + + - name: Create Pull Request + id: pr + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: "update-supported-versions" + commit-message: "Update supported versions table" + delete-branch: true + base: main + title: "chore: update supported versions" + labels: changelog/no-changelog + body: | + Generates / updates the supported versions table for integrations. + This should be tied to releases, or triggered manually. + Workflow runs: [Generate Supported Integration Versions](https://github.com/DataDog/dd-trace-py/actions/workflows/generate-supported-versions.yml) + + ## Checklist + - [x] PR author has checked that all the criteria below are met + - The PR description includes an overview of the change + - The PR description articulates the motivation for the change + - The change includes tests OR the PR description describes a testing strategy + - The PR description notes risks associated with the change, if any + - Newly-added code is easy to change + - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) + - The change includes or references documentation updates if necessary + - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) + + ## Reviewer Checklist + - [ ] Reviewer has checked that all the criteria below are met + - Title is accurate + - All changes are related to the pull request's stated goal + - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes + - Testing strategy adequately addresses listed risks + - Newly-added code is easy to change + - Release note makes sense to a user of the library + - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment + - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) diff --git a/scripts/freshvenvs.py b/scripts/freshvenvs.py index 61a381d8fa6..13cd81a6fcc 100644 --- a/scripts/freshvenvs.py +++ b/scripts/freshvenvs.py @@ -4,6 +4,7 @@ from http.client import HTTPSConnection from io import StringIO import json +from operator import itemgetter import os import pathlib import sys @@ -21,7 +22,9 @@ CONTRIB_ROOT = pathlib.Path("ddtrace/contrib") LATEST = "" +excluded = {"coverage"} suite_to_package = { + "kafka": "confluent-kafka", "consul": "python-consul", "snowflake": "snowflake-connector-python", "flask_cache": "flask-caching", @@ -30,11 +33,35 @@ "asyncio": "pytest-asyncio", "sqlite3": "pysqlite3-binary", "grpc": "grpcio", + "google_generativeai": "google-generativeai", "psycopg2": "psycopg2-binary", "cassandra": "cassandra-driver", "rediscluster": "redis-py-cluster", + "dogpile_cache": "dogpile-cache", + "vertica": "vertica_python", } + +# mapping the name of the module to the name of the package (on pypi and as defined in lockfiles) +mapping_module_to_package = { + "confluent_kafka": "confluent-kafka", + "snowflake": "snowflake-connector-python", + "cassandra": "cassandra-driver", + "rediscluster": "redis-py-cluster", + "vertica_python": "vertica-python", + "flask_cache": "flask-cache", + "flask_caching": "flask-caching", + "consul": "python-consul", + "grpc": "grpcio", + "graphql": "graphql-core", + "mysql": "pymysql", +} + + +supported_versions = [] # list of dicts +pinned_packages = set() + + class Capturing(list): def __enter__(self): self._stdout = sys.stdout @@ -77,14 +104,16 @@ def _get_riot_envs_including_any(modules: typing.Set[str]) -> typing.Set[str]: with open(f".riot/requirements/{item}", "r") as lockfile: lockfile_content = lockfile.read() for module in modules: - if module in lockfile_content: + if module in lockfile_content or ( + module in suite_to_package and suite_to_package[module] in lockfile_content + ): envs |= {item.split(".")[0]} break return envs def _get_updatable_packages_implementing(modules: typing.Set[str]) -> typing.Set[str]: - """Return all packages that can be updated and have contribs implemented for them""" + """Return all packages have contribs implemented for them""" all_venvs = riotfile.venv.venvs for v in all_venvs: @@ -92,12 +121,18 @@ def _get_updatable_packages_implementing(modules: typing.Set[str]) -> typing.Set if package not in modules: continue if not _venv_sets_latest_for_package(v, package): - modules.remove(package) + pinned_packages.add(package) packages = {m for m in modules if "." not in m} return packages +def _get_all_modules(modules: typing.Set[str]) -> typing.Set[str]: + """Return all packages have contribs implemented for them""" + contrib_modules = {m for m in modules if "." not in m} + return contrib_modules + + def _get_version_extremes(package_name: str) -> typing.Tuple[Optional[str], Optional[str]]: """Return the (earliest, latest) supported versions of a given package""" with Capturing() as output: @@ -134,16 +169,27 @@ def _get_version_extremes(package_name: str) -> typing.Tuple[Optional[str], Opti def _get_package_versions_from(env: str, packages: typing.Set[str]) -> typing.List[typing.Tuple[str, str]]: - """Return the list of package versions that are tested""" + """Return the list of package versions that are tested, related to the modules""" + # Returns [(package, version), (package, versions)] lockfile_content = pathlib.Path(f".riot/requirements/{env}.txt").read_text().splitlines() lock_packages = [] for line in lockfile_content: package, _, versions = line.partition("==") + # remap the package -> module name if package in packages: lock_packages.append((package, versions)) + return lock_packages +def _is_module_autoinstrumented(module: str) -> bool: + import importlib + + _monkey = importlib.import_module("ddtrace._monkey") + PATCH_MODULES = getattr(_monkey, "PATCH_MODULES") + + return module in PATCH_MODULES and PATCH_MODULES[module] + def _versions_fully_cover_bounds(bounds: typing.Tuple[str, str], versions: typing.List[str]) -> bool: """Return whether the tested versions cover the full range of supported versions""" if not versions: @@ -173,12 +219,25 @@ def _venv_sets_latest_for_package(venv: riotfile.Venv, suite_name: str) -> bool: return False -def main(): - all_required_modules = _get_integrated_modules() - all_required_packages = _get_updatable_packages_implementing(all_required_modules) - envs = _get_riot_envs_including_any(all_required_modules) +def _get_all_used_versions(envs, packages) -> dict: + # Returns dict(module, set(versions)) for a venv, as defined in riotfiles. + all_used_versions = defaultdict(set) + for env in envs: + versions_used = _get_package_versions_from(env, packages) # returns list of (package, versions) + for package, version in versions_used: + all_used_versions[package].add(version) + return all_used_versions + +def _get_version_bounds(packages) -> dict: + # Return dict(module: (earliest, latest)) of the module on PyPI bounds = dict() + for package in packages: + earliest, latest = _get_version_extremes(package) + bounds[package] = (earliest, latest) + return bounds + +def output_outdated_packages(all_required_packages, envs, bounds): for package in all_required_packages: earliest, latest = _get_version_extremes(package) bounds[package] = (earliest, latest) @@ -194,10 +253,55 @@ def main(): if not ordered: continue if not _versions_fully_cover_bounds(bounds[package], ordered): - print( - f"{package}: policy supports version {bounds[package][0]} through {bounds[package][1]} " - f"but only these versions are used: {[str(v) for v in ordered]}" - ) + print(f"{package}") + +def generate_supported_versions(contrib_packages, all_used_versions, patched): + for mod in mapping_module_to_package: + contrib_packages.remove(mod) + contrib_packages.add(mapping_module_to_package[mod]) + patched[mapping_module_to_package[mod]] = _is_module_autoinstrumented(mod) + + # Generate supported versions + for package in contrib_packages: + ordered = sorted([Version(v) for v in all_used_versions[package]], reverse=True) + if not ordered: + continue + json_format = { + "integration": package, + "minimum_tracer_supported": str(ordered[-1]), + "max_tracer_supported": str(ordered[0]), + } + + if package in pinned_packages: + json_format["pinned"] = "true" + + if package not in patched: + patched[package] = _is_module_autoinstrumented(package) + json_format["auto-instrumented"] = patched[package] + supported_versions.append(json_format) + + supported_versions_output = sorted(supported_versions, key=itemgetter("integration")) + with open("supported_versions_output.json", "w") as file: + json.dump(supported_versions_output, file, indent=4) + +def main(): + all_required_modules = _get_integrated_modules() + all_required_packages = _get_updatable_packages_implementing(all_required_modules) # these are MODULE names + contrib_modules = _get_all_modules(all_required_modules) + envs = _get_riot_envs_including_any(all_required_modules) + patched = {} + + contrib_packages = contrib_modules + all_used_versions = _get_all_used_versions(envs, contrib_packages) + bounds = _get_version_bounds(contrib_packages) + + if len(sys.argv) != 2: + print("usage: python scripts/freshvenvs.py or ") + return + if sys.argv[1] == "output": + output_outdated_packages(all_required_packages, envs, bounds) + if sys.argv[1] == "generate": + generate_supported_versions(contrib_packages, all_used_versions, patched) if __name__ == "__main__": diff --git a/scripts/generate_table.py b/scripts/generate_table.py new file mode 100644 index 00000000000..1d7569b3e63 --- /dev/null +++ b/scripts/generate_table.py @@ -0,0 +1,24 @@ +import csv +import json + + +print("Reading supported_versions_output.json") + +with open("supported_versions_output.json", "r") as json_file: + data = json.load(json_file) + +columns = ["integration", "minimum_tracer_supported", "max_tracer_supported", "auto-instrumented"] +csv_rows = [] + +for entry in data: + integration_name = entry.get("integration", "") + if entry.get("pinned", "").lower() == "true": + integration_name += " *" + entry["integration"] = integration_name + csv_rows.append({col: entry.get(col, "") for col in columns}) + +with open("supported_versions_table.csv", "w", newline="") as csv_file: + print("Wrote to supported_versions_table.csv") + writer = csv.DictWriter(csv_file, fieldnames=columns) + writer.writeheader() + writer.writerows(csv_rows) diff --git a/scripts/regenerate-riot-latest.sh b/scripts/regenerate-riot-latest.sh index f0e68938a27..423a0524891 100755 --- a/scripts/regenerate-riot-latest.sh +++ b/scripts/regenerate-riot-latest.sh @@ -3,7 +3,7 @@ set -e DDTEST_CMD=scripts/ddtest -pkgs=$(python scripts/freshvenvs.py | cut -d':' -f1) +pkgs=$(python scripts/freshvenvs.py output) echo $pkgs if ! $DDTEST_CMD; then @@ -20,7 +20,8 @@ for pkg in ${pkgs[*]}; do echo "No riot hashes found for pattern: $VENV_NAME" else echo "VENV_NAME=$VENV_NAME" >> $GITHUB_ENV - for h in ${RIOT_HASHES[@]}; do + for h in ${RIOT_HASHES[@]}; do + echo "Removing riot lockfiles" rm ".riot/requirements/${h}.txt" done scripts/compile-and-prune-test-requirements From 494c0394aaed8e3fa81a3117557723a50527dd64 Mon Sep 17 00:00:00 2001 From: wantsui Date: Thu, 19 Dec 2024 15:51:59 -0500 Subject: [PATCH 346/372] chore: remove expired until from django snapshot test (#11763) The `until` timestamp in the flaky decorator on Django snapshots has been expired since Jan 2024, which was uncovered by https://github.com/DataDog/dd-trace-py/pull/11274 As seen in this failed run: https://gitlab.ddbuild.io/DataDog/apm-reliability/dd-trace-py/-/jobs/742978251, if we remove it, the current failure is on: > meta mismatch on '_dd.base_service': got 'tests.contrib.django' which does not match expected ''. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/contrib/django/test_django_snapshots.py | 1 - ...t_middleware_trace_partial_based_view.json | 52 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/tests/contrib/django/test_django_snapshots.py b/tests/contrib/django/test_django_snapshots.py index feaead253d8..d7402e37083 100644 --- a/tests/contrib/django/test_django_snapshots.py +++ b/tests/contrib/django/test_django_snapshots.py @@ -107,7 +107,6 @@ def test_middleware_trace_callable_view(client): assert client.get("/feed-view/").status_code == 200 -@flaky(until=1706677200) @pytest.mark.skipif( sys.version_info >= (3, 10, 0), reason=("func_name changed with Python 3.10 which changes the resource name." "TODO: new snapshot required."), diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_partial_based_view.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_partial_based_view.json index 9b21d7f1b84..8b56757961b 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_partial_based_view.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_middleware_trace_partial_based_view.json @@ -9,7 +9,7 @@ "type": "web", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", "component": "django", @@ -45,7 +45,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -62,7 +62,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -79,7 +79,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -96,7 +96,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -113,7 +113,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -130,7 +130,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -147,7 +147,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -164,7 +164,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -181,7 +181,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -198,7 +198,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -215,7 +215,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -232,7 +232,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -249,7 +249,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -266,7 +266,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -283,7 +283,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -300,7 +300,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -317,7 +317,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -334,7 +334,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -351,7 +351,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -368,7 +368,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -385,7 +385,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -402,7 +402,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -419,7 +419,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -436,7 +436,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, @@ -453,7 +453,7 @@ "type": "", "error": 0, "meta": { - "_dd.base_service": "", + "_dd.base_service": "tests.contrib.django", "_dd.p.tid": "654a694400000000", "component": "django" }, From d197a00c6a15b73ca2ea4d6daa7c5b7f91cf5ff3 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 19 Dec 2024 15:59:19 -0500 Subject: [PATCH 347/372] chore(profiling): native tests w/ valgrind (#11750) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: David Sanchez <838104+sanchda@users.noreply.github.com> --- .github/workflows/profiling-native.yml | 6 +++++- .../datadog/profiling/build_standalone.sh | 16 +++++++++++++++- .../profiling/dd_wrapper/test/CMakeLists.txt | 13 +++++++++++++ .../profiling/dd_wrapper/test/valgrind.supp | 7 +++++++ .../profiling/stack_v2/test/CMakeLists.txt | 13 +++++++++++++ .../profiling/stack_v2/test/valgrind.supp | 7 +++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 ddtrace/internal/datadog/profiling/dd_wrapper/test/valgrind.supp create mode 100644 ddtrace/internal/datadog/profiling/stack_v2/test/valgrind.supp diff --git a/.github/workflows/profiling-native.yml b/.github/workflows/profiling-native.yml index 98722552dbd..280d586d36e 100644 --- a/.github/workflows/profiling-native.yml +++ b/.github/workflows/profiling-native.yml @@ -20,7 +20,7 @@ jobs: matrix: os: [ubuntu-24.04] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - sanitizer: ["safety", "thread"] + sanitizer: ["safety", "thread", "valgrind"] steps: - uses: actions/checkout@v4 @@ -40,6 +40,10 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh 19 + - name: Install Valgrind + run: | + sudo apt-get install -y valgrind + - name: Run tests with sanitizers run: | # DEV: We currently have tests in dd_wrapper and stack_v2, setting diff --git a/ddtrace/internal/datadog/profiling/build_standalone.sh b/ddtrace/internal/datadog/profiling/build_standalone.sh index beeda4f21b4..c7bc4c14af9 100755 --- a/ddtrace/internal/datadog/profiling/build_standalone.sh +++ b/ddtrace/internal/datadog/profiling/build_standalone.sh @@ -94,6 +94,9 @@ compiler_args["cppcheck"]="-DDO_CPPCHECK=ON" compiler_args["infer"]="-DDO_INFER=ON" compiler_args["clangtidy"]="-DDO_CLANGTIDY=ON" compiler_args["clangtidy_cmd"]="-DCLANGTIDY_CMD=${CLANGTIDY_CMD}" +compiler_args["valgrind"]="-DDO_VALGRIND=ON" + +ctest_args=() # Initial cmake args cmake_args=( @@ -169,7 +172,7 @@ run_cmake() { fi if [[ " ${cmake_args[*]} " =~ " -DBUILD_TESTING=ON " ]]; then echo "--------------------------------------------------------------------- Running Tests" - ctest --output-on-failure || { echo "tests failed!"; exit 1; } + ctest ${ctest_args[*]} --output-on-failure || { echo "tests failed!"; exit 1; } fi # OK, the build or whatever went fine I guess. @@ -223,6 +226,10 @@ print_cmake_args() { echo "Targets: ${targets[*]}" } +print_ctest_args() { + echo "CTest Args: ${ctest_args[*]}" +} + ### Check input # Check the first slot, options add_compiler_args() { @@ -263,6 +270,11 @@ add_compiler_args() { cmake_args+=(${compiler_args["memory"]}) set_clang ;; + --valgrind) + cmake_args+=(${compiler_args["valgrind"]}) + ctest_args+="-T memcheck" + set_clang + ;; -C|--cppcheck) cmake_args+=(${compiler_args["cppcheck"]}) set_clang @@ -369,6 +381,8 @@ add_target "$3" # Print cmake args print_cmake_args +print_ctest_args + # Run cmake for target in "${targets[@]}"; do run_cmake $target diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index b80ace74968..299ba8812f8 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -12,6 +12,19 @@ FetchContent_MakeAvailable(googletest) include(GoogleTest) include(AnalysisFunc) +if(DO_VALGRIND) + find_program(VALGRIND_EXECUTABLE NAMES valgrind PATHS /usr/bin /usr/local/bin) + + if (VALGRIND_EXECUTABLE) + set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}") + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp") + else() + message(FATAL_ERROR "Valgrind not found") + endif() + + include(CTest) +endif() + FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) FetchContent_MakeAvailable(json) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/valgrind.supp b/ddtrace/internal/datadog/profiling/dd_wrapper/test/valgrind.supp new file mode 100644 index 00000000000..d8534d2a228 --- /dev/null +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/valgrind.supp @@ -0,0 +1,7 @@ +{ + ddcommon_uninitialized_value + Memcheck:Cond + fun:eq + ... + fun:*ddcommon*entity_id*unix*container_id* +} diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt index 05176f2c803..d6e585f2c9f 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -12,6 +12,19 @@ FetchContent_MakeAvailable(googletest) include(GoogleTest) include(AnalysisFunc) +if(DO_VALGRIND) + find_program(VALGRIND_EXECUTABLE NAMES valgrind PATHS /usr/bin /usr/local/bin) + + if (VALGRIND_EXECUTABLE) + set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}") + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp") + else() + message(FATAL_ERROR "Valgrind not found") + endif() + + include(CTest) +endif() + function(dd_wrapper_add_test name) add_executable(${name} ${ARGN}) target_include_directories(${name} PRIVATE ../include) diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/valgrind.supp b/ddtrace/internal/datadog/profiling/stack_v2/test/valgrind.supp new file mode 100644 index 00000000000..d8534d2a228 --- /dev/null +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/valgrind.supp @@ -0,0 +1,7 @@ +{ + ddcommon_uninitialized_value + Memcheck:Cond + fun:eq + ... + fun:*ddcommon*entity_id*unix*container_id* +} From f810101aa2d83256bcdcca250fbebbcc37e690ef Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 19 Dec 2024 16:32:23 -0500 Subject: [PATCH 348/372] ci: optimize ci runtime (#11798) --- .gitlab/package.yml | 19 ------------------- .gitlab/tests.yml | 14 +++----------- hatch.toml | 7 +++++-- scripts/gen_gitlab_config.py | 9 +-------- tests/suitespec.yml | 8 ++++++++ 5 files changed, 17 insertions(+), 40 deletions(-) diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 0cf300d7cbd..973e2d55d3f 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -1,22 +1,3 @@ -build_base_venvs: - extends: .testrunner - stage: package - parallel: - matrix: - - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] - variables: - CMAKE_BUILD_PARALLEL_LEVEL: 12 - PIP_VERBOSE: 1 - script: - - pip install riot==0.20.0 - - riot -P -v generate --python=$PYTHON_VERSION - artifacts: - name: venv_$PYTHON_VERSION - paths: - - .riot/venv_* - - ddtrace/**/*.so* - - ddtrace/internal/datadog/profiling/crashtracker/crashtracker_exe* - download_ddtrace_artifacts: image: registry.ddbuild.io/github-cli:v27480869-eafb11d-2.43.0 tags: [ "arch:amd64" ] diff --git a/.gitlab/tests.yml b/.gitlab/tests.yml index d38a22cf0ff..b8c9a3d9897 100644 --- a/.gitlab/tests.yml +++ b/.gitlab/tests.yml @@ -10,17 +10,9 @@ variables: PYTEST_ADDOPTS: "-s" # CI_DEBUG_SERVICES: "true" -.testrunner: - image: registry.ddbuild.io/images/mirror/dd-trace-py/testrunner:0a50e839f4b1600f02157518b8d016451b346578@sha256:5dae9bc7872f69b31b612690f0748c7ad71ab90ef28a754b2ae93d0ba505837b - # DEV: we have a larger pool of amd64 runners, prefer that over arm64 - tags: [ "arch:amd64" ] - timeout: 20m - before_script: - - pyenv global 3.12 3.7 3.8 3.9 3.10 3.11 3.13 - - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ - - -{{services.yml}} +include: + - local: ".gitlab/services.yml" + - local: ".gitlab/testrunner.yml" .test_base_hatch: extends: .testrunner diff --git a/hatch.toml b/hatch.toml index ff11ec3f743..614054dbfed 100644 --- a/hatch.toml +++ b/hatch.toml @@ -154,9 +154,12 @@ extra-dependencies = [ "pytest-cov", "hypothesis<6.45.1" ] +[envs.meta-testing.env-vars] +DD_CIVISIBILITY_FLAKY_RETRY_ENABLED = "0" + [envs.meta-testing.scripts] -meta-testing = [ - "pytest {args} tests/meta" +test = [ + "pytest {args} --no-ddtrace tests/meta" ] [envs.integration_test] diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 22b236ddfc5..8dc9e5b178f 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -95,9 +95,7 @@ def gen_required_suites() -> None: circleci_jobs = set(circleci_config["jobs"].keys()) # Copy the template file - TESTS_GEN.write_text( - (GITLAB / "tests.yml").read_text().replace(r"{{services.yml}}", (GITLAB / "services.yml").read_text()) - ) + TESTS_GEN.write_text((GITLAB / "tests.yml").read_text()) # Generate the list of suites to run with TESTS_GEN.open("a") as f: for suite in required_suites: @@ -162,11 +160,6 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: command="hatch run lint:suitespec-check", paths={"*"}, ) - check( - name="conftest", - command="hatch run meta-testing:meta-testing", - paths={"**conftest.py"}, - ) # ----------------------------------------------------------------------------- diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 4b13005d662..41fabd7aa88 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -151,6 +151,14 @@ components: vendor: - ddtrace/vendor/* suites: + conftest: + parallelism: 1 + paths: + - 'conftest.py' + - '**/conftest.py' + pattern: meta-testing + runner: hatch + snapshot: false ddtracerun: parallelism: 6 paths: From 5cee25e39d9b8ee0f2db1816a265979c183715a1 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Thu, 19 Dec 2024 18:06:24 -0500 Subject: [PATCH 349/372] ci: cmake format fix (#11809) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../datadog/profiling/dd_wrapper/test/CMakeLists.txt | 11 ++++++++--- .../datadog/profiling/stack_v2/test/CMakeLists.txt | 11 ++++++++--- scripts/gen_circleci_config.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt index 299ba8812f8..66dac6b6f0d 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/test/CMakeLists.txt @@ -13,11 +13,16 @@ include(GoogleTest) include(AnalysisFunc) if(DO_VALGRIND) - find_program(VALGRIND_EXECUTABLE NAMES valgrind PATHS /usr/bin /usr/local/bin) + find_program( + VALGRIND_EXECUTABLE + NAMES valgrind + PATHS /usr/bin /usr/local/bin) - if (VALGRIND_EXECUTABLE) + if(VALGRIND_EXECUTABLE) set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}") - set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp") + set(MEMORYCHECK_COMMAND_OPTIONS + "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp" + ) else() message(FATAL_ERROR "Valgrind not found") endif() diff --git a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt index d6e585f2c9f..423f927d8f1 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/stack_v2/test/CMakeLists.txt @@ -13,11 +13,16 @@ include(GoogleTest) include(AnalysisFunc) if(DO_VALGRIND) - find_program(VALGRIND_EXECUTABLE NAMES valgrind PATHS /usr/bin /usr/local/bin) + find_program( + VALGRIND_EXECUTABLE + NAMES valgrind + PATHS /usr/bin /usr/local/bin) - if (VALGRIND_EXECUTABLE) + if(VALGRIND_EXECUTABLE) set(MEMORYCHECK_COMMAND "${VALGRIND_EXECUTABLE}") - set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp") + set(MEMORYCHECK_COMMAND_OPTIONS + "--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --trace-children=yes --error-exitcode=1 --log-fd=1 --suppressions=${CMAKE_CURRENT_SOURCE_DIR}/valgrind.supp" + ) else() message(FATAL_ERROR "Valgrind not found") endif() diff --git a/scripts/gen_circleci_config.py b/scripts/gen_circleci_config.py index 7225ea38d22..627a3715427 100644 --- a/scripts/gen_circleci_config.py +++ b/scripts/gen_circleci_config.py @@ -51,7 +51,7 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: check( name="Style", command="hatch run lint:style", - paths={"docker*", "*.py", "*.pyi", "hatch.toml", "pyproject.toml", "*.cpp", "*.h"}, + paths={"docker*", "*.py", "*.pyi", "hatch.toml", "pyproject.toml", "*.cpp", "*.h", "CMakeLists.txt"}, ) check( name="Typing", From 350307ce2ad70d6879ec5d53e3b7246209f23098 Mon Sep 17 00:00:00 2001 From: Thomas Hunter II Date: Thu, 19 Dec 2024 15:33:46 -0800 Subject: [PATCH 350/372] repo: mandatory issue templates (AIDM-423) (#11765) - adds two two issue templates - disables freeform issues - deletes an unused issue template file - adds a security policy file - standardizes the create issue experience across tracers - see the [node.js create issue screen](https://github.com/DataDog/dd-trace-js/issues/new/choose) for an example of how this will work --- .github/ISSUE_TEMPLATE.md | 29 ---------- .github/ISSUE_TEMPLATE/bug_report.yaml | 64 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 9 +++ .github/ISSUE_TEMPLATE/feature_request.yaml | 50 ++++++++++++++++ SECURITY.md | 15 +++++ 5 files changed, 138 insertions(+), 29 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 879365fe13c..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,29 +0,0 @@ - - -### Summary of problem - -### Which version of dd-trace-py are you using? - -### Which version of pip are you using? - - -### Which libraries and their versions are you using? - -
- `pip freeze` - -
- -### How can we reproduce your problem? - -### What is the result that you get? - -### What is the result that you expected? diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000000..1870fa32021 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,64 @@ +name: "Bug Report (Low Priority)" +description: "Create a public Bug Report. Note that these may not be addressed as quickly as the helpdesk and that looking up account information will be difficult." +title: "[BUG]: " +labels: bug +body: + - type: input + attributes: + label: Tracer Version(s) + description: "Version(s) of the tracer affected by this bug" + placeholder: "1.23.4, 2.8.0" + validations: + required: true + + - type: input + attributes: + label: Python Version(s) + description: "Version(s) of Python (`python --version`) that you've encountered this bug with" + placeholder: "Python 3.9.15" + validations: + required: true + + - type: input + attributes: + label: Pip Version(s) + description: "Version(s) of Pip (`pip --version`) that you've encountered this bug with" + placeholder: "pip 22.0.4" + validations: + required: true + + - type: textarea + attributes: + label: Bug Report + description: Please add a clear and concise description of the bug here + validations: + required: true + + - type: textarea + attributes: + label: Reproduction Code + description: Please add code here to help us reproduce the problem + validations: + required: false + + - type: textarea + attributes: + label: Error Logs + description: "Please provide any error logs from the tracer (`DD_TRACE_DEBUG=true` can help)" + validations: + required: false + + - type: textarea + attributes: + label: Libraries in Use + description: "Which libraries and their versions are you using? Paste output from `pip freeze` here." + validations: + required: false + + - type: input + attributes: + label: Operating System + description: "Provide your operating system and version (e.g. `uname -a`)" + placeholder: Darwin Kernel Version 23.6.0 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 00000000000..8c090d6d6d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,9 @@ +blank_issues_enabled: false +contact_links: + - name: Bug Report (High Priority) + url: https://help.datadoghq.com/hc/en-us/requests/new?tf_1260824651490=pt_product_type:apm&tf_1900004146284=pt_apm_language:python + about: Create an expedited Bug Report via the helpdesk (no login required). This will allow us to look up your account and allows you to provide additional information in private. Please do not create a GitHub issue to report a bug. + - name: Feature Request (High Priority) + url: https://help.datadoghq.com/hc/en-us/requests/new?tf_1260824651490=pt_product_type:apm&tf_1900004146284=pt_apm_language:python&tf_1260825272270=pt_apm_category_feature_request + about: Create an expedited Feature Request via the helpdesk (no login required). This helps with prioritization and allows you to provide additional information in private. Please do not create a GitHub issue to request a feature. + diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 00000000000..736852848cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,50 @@ +name: Feature Request (Low Priority) +description: Create a public Feature Request. Note that these may not be addressed as quickly as the helpdesk and that looking up account information will be difficult. +title: "[FEATURE]: " +labels: feature-request +body: + - type: input + attributes: + label: Package Name + description: "If your feature request is to add instrumentation support for a package please provide the name here" + placeholder: mysql + validations: + required: false + + - type: input + attributes: + label: Package Version(s) + description: "If your feature request is to add instrumentation support for a package please provide the version you use" + placeholder: 0.0.3 + validations: + required: false + + - type: textarea + attributes: + label: Describe the goal of the feature + description: A clear and concise goal of what you want to happen. + validations: + required: true + + - type: textarea + attributes: + label: Is your feature request related to a problem? + description: | + Please add a clear and concise description of your problem. + E.g. I'm unable to instrument my database queries... + validations: + required: false + + - type: textarea + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered + validations: + required: false + + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here + validations: + required: false diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..d57cdcb881a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +This document outlines the security policy for the Datadog Python client library (aka Python tracer) and what to do if you discover a security vulnerability in the project. +Most notably, please do not share the details in a public forum (such as in a discussion, issue, or pull request) but instead reach out to us with the details. +This gives us an opportunity to release a fix for others to benefit from by the time details are made public. + +## Supported Versions + +We accept vulnerability submissions for the [currently maintained release](https://github.com/DataDog/dd-trace-py/releases). + +## Reporting a Vulnerability + +If you discover a vulnerability in the Datadog Python client library (or any Datadog product for that matter) please submit details to the following email address: + +* [security@datadoghq.com](mailto:security@datadoghq.com) From 20b2b0316f89f476fe00d959ef0eadedc88fa785 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:50:30 -0800 Subject: [PATCH 351/372] fix(ci): Revert CI fix (#11807) This reverts commit fb82c64f516048e7146ce6a5bcd8961a6cded29a. [This merge commit](https://github.com/DataDog/dd-trace-py/commit/fb82c64f516048e7146ce6a5bcd8961a6cded29a) seems to have broken the suite-selection mechanism in CircleCI ([example](https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/79433/workflows/cc067cf4-bb45-4f8d-8a60-f43e95041847)) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- scripts/gen_circleci_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/gen_circleci_config.py b/scripts/gen_circleci_config.py index 627a3715427..bc51f2c5519 100644 --- a/scripts/gen_circleci_config.py +++ b/scripts/gen_circleci_config.py @@ -17,9 +17,10 @@ def gen_required_suites(template: dict) -> None: required_suites = template["requires_tests"]["requires"] = [] for_each_testrun_needed( suites=sorted( - set(n for n, s in get_suites().items() if not s.get("skip", False)) & set(template["jobs"].keys()) + set(n.rpartition("::")[-1] for n, s in get_suites().items() if not s.get("skip", False)) + & set(template["jobs"].keys()) ), - action=lambda suite: required_suites.append(suite.rpartition("::")[-1]), + action=lambda suite: required_suites.append(suite), git_selections=extract_git_commit_selections(os.getenv("GIT_COMMIT_DESC", "")), ) From 3f64666a83f77f3c0b8e366181589286283e83fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20De=20Ara=C3=BAjo?= Date: Fri, 20 Dec 2024 15:03:19 +0000 Subject: [PATCH 352/372] chore(ci_visibility): add quarantine support to pytest (#11615) This PR adds preliminary support for quarantining tests in pytest. The API to query which tests are quarantined does not exist yet on the backend side, and the final form of that API is still to be defined, so the code dealing with the API has been moved to a separate PR (https://github.com/DataDog/dd-trace-py/pull/11770). Currently, we can mark tests as quarantined by manually adding the `test.quarantine.is_quarantined` tag to the test with a pytest decorator: ``` @pytest.mark.dd_tags(**{"test.quarantine.is_quarantined": True}) def test_fail_quarantined(): assert False ``` For testing purposes, the environment variables `_DD_TEST_FORCE_ENABLE_QUARANTINE` and `_DD_TEST_FORCE_ENABLE_ATR` have been added to enable quarantine and ATR without depending on the backend. The test reporting looks like below. Errors and logs for quarantined tests are not printed. ![image](https://github.com/user-attachments/assets/f070323d-edef-431e-a7a4-a6d119348876) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_atr_utils.py | 117 +++++-- ddtrace/contrib/pytest/_plugin_v2.py | 35 ++- ddtrace/contrib/pytest/_retry_utils.py | 4 +- ddtrace/internal/ci_visibility/_api_client.py | 20 +- ddtrace/internal/ci_visibility/api/_base.py | 12 + .../internal/ci_visibility/api/_session.py | 6 + ddtrace/internal/ci_visibility/api/_suite.py | 2 +- ddtrace/internal/ci_visibility/api/_test.py | 23 +- ddtrace/internal/ci_visibility/constants.py | 5 + ddtrace/internal/ci_visibility/recorder.py | 51 ++- .../ci_visibility/telemetry/events.py | 8 +- .../internal/ci_visibility/telemetry/git.py | 3 + ddtrace/internal/test_visibility/api.py | 10 + tests/ci_visibility/api_client/_util.py | 17 + tests/ci_visibility/test_encoder.py | 44 +-- tests/ci_visibility/test_quarantine.py | 56 ++++ tests/contrib/pytest/test_pytest_atr.py | 4 - tests/contrib/pytest/test_pytest_efd.py | 4 - .../contrib/pytest/test_pytest_quarantine.py | 294 ++++++++++++++++++ 19 files changed, 626 insertions(+), 89 deletions(-) create mode 100644 tests/ci_visibility/test_quarantine.py create mode 100644 tests/contrib/pytest/test_pytest_quarantine.py diff --git a/ddtrace/contrib/pytest/_atr_utils.py b/ddtrace/contrib/pytest/_atr_utils.py index 89be8b881af..0d684486602 100644 --- a/ddtrace/contrib/pytest/_atr_utils.py +++ b/ddtrace/contrib/pytest/_atr_utils.py @@ -29,33 +29,56 @@ class _ATR_RETRY_OUTCOMES: ATR_FINAL_FAILED = "dd_atr_final_failed" +class _QUARANTINE_ATR_RETRY_OUTCOMES(_ATR_RETRY_OUTCOMES): + ATR_ATTEMPT_PASSED = "dd_quarantine_atr_attempt_passed" + ATR_ATTEMPT_FAILED = "dd_quarantine_atr_attempt_failed" + ATR_ATTEMPT_SKIPPED = "dd_quarantine_atr_attempt_skipped" + ATR_FINAL_PASSED = "dd_quarantine_atr_final_passed" + ATR_FINAL_FAILED = "dd_quarantine_atr_final_failed" + + _FINAL_OUTCOMES: t.Dict[TestStatus, str] = { TestStatus.PASS: _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, TestStatus.FAIL: _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, } +_QUARANTINE_FINAL_OUTCOMES: t.Dict[TestStatus, str] = { + TestStatus.PASS: _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, + TestStatus.FAIL: _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, +} + + def atr_handle_retries( test_id: InternalTestId, item: pytest.Item, when: str, original_result: pytest_TestReport, test_outcome: _TestOutcome, + is_quarantined: bool = False, ): + if is_quarantined: + retry_outcomes = _QUARANTINE_ATR_RETRY_OUTCOMES + final_outcomes = _QUARANTINE_FINAL_OUTCOMES + else: + retry_outcomes = _ATR_RETRY_OUTCOMES + final_outcomes = _FINAL_OUTCOMES + + outcomes = RetryOutcomes( + PASSED=retry_outcomes.ATR_ATTEMPT_PASSED, + FAILED=retry_outcomes.ATR_ATTEMPT_FAILED, + SKIPPED=retry_outcomes.ATR_ATTEMPT_SKIPPED, + XFAIL=retry_outcomes.ATR_ATTEMPT_PASSED, + XPASS=retry_outcomes.ATR_ATTEMPT_FAILED, + ) + # Overwrite the original result to avoid double-counting when displaying totals in final summary if when == "call": if test_outcome.status == TestStatus.FAIL: - original_result.outcome = _ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED - return - if InternalTest.get_tag(test_id, "_dd.ci.atr_setup_failed"): - log.debug("Test item %s failed during setup, will not be retried for Early Flake Detection") - return - if InternalTest.get_tag(test_id, "_dd.ci.atr_teardown_failed"): - # NOTE: tests that passed their call but failed during teardown are not retried - log.debug("Test item %s failed during teardown, will not be retried for Early Flake Detection") + original_result.outcome = outcomes.FAILED return - atr_outcome = _atr_do_retries(item) + atr_outcome = _atr_do_retries(item, outcomes) final_report = pytest_TestReport( nodeid=item.nodeid, @@ -63,7 +86,7 @@ def atr_handle_retries( keywords=item.keywords, when="call", longrepr=None, - outcome=_FINAL_OUTCOMES[atr_outcome], + outcome=final_outcomes[atr_outcome], ) item.ihook.pytest_runtest_logreport(report=final_report) @@ -72,15 +95,7 @@ def atr_get_failed_reports(terminalreporter: _pytest.terminal.TerminalReporter) return terminalreporter.getreports(_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED) -def _atr_do_retries(item: pytest.Item) -> TestStatus: - outcomes = RetryOutcomes( - PASSED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, - FAILED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, - SKIPPED=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, - XFAIL=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, - XPASS=_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, - ) - +def _atr_do_retries(item: pytest.Item, outcomes: RetryOutcomes) -> TestStatus: test_id = _get_test_id_from_item(item) while InternalTest.atr_should_retry(test_id): @@ -160,21 +175,21 @@ def atr_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.Te _atr_write_report_for_status( terminalreporter, - _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, - "failed", - PYTEST_STATUS.FAILED, - raw_summary_strings, - markedup_summary_strings, + status_key=_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, + status_text="failed", + report_outcome=PYTEST_STATUS.FAILED, + raw_strings=raw_summary_strings, + markedup_strings=markedup_summary_strings, color="red", ) _atr_write_report_for_status( terminalreporter, - _ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, - "passed", - PYTEST_STATUS.PASSED, - raw_summary_strings, - markedup_summary_strings, + status_key=_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, + status_text="passed", + report_outcome=PYTEST_STATUS.PASSED, + raw_strings=raw_summary_strings, + markedup_strings=markedup_summary_strings, color="green", ) @@ -268,3 +283,47 @@ def atr_get_teststatus(report: pytest_TestReport) -> _pytest_report_teststatus_r if report.outcome == _ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED: return (_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, "F", ("ATR FINAL STATUS: FAILED", {"red": True})) return None + + +def quarantine_atr_get_teststatus(report: pytest_TestReport) -> _pytest_report_teststatus_return_type: + if report.outcome == _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED: + return ( + _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, + "q", + (f"QUARANTINED RETRY {_get_retry_attempt_string(report.nodeid)}PASSED", {"blue": True}), + ) + if report.outcome == _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED: + return ( + _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, + "Q", + (f"QUARANTINED RETRY {_get_retry_attempt_string(report.nodeid)}FAILED", {"blue": True}), + ) + if report.outcome == _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED: + return ( + _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, + "q", + (f"QUARANTINED RETRY {_get_retry_attempt_string(report.nodeid)}SKIPPED", {"blue": True}), + ) + if report.outcome == _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED: + return ( + _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, + ".", + ("QUARANTINED FINAL STATUS: PASSED", {"blue": True}), + ) + if report.outcome == _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED: + return ( + _QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, + "F", + ("QUARANTINED FINAL STATUS: FAILED", {"blue": True}), + ) + return None + + +def quarantine_pytest_terminal_summary_post_yield(terminalreporter: _pytest.terminal.TerminalReporter): + terminalreporter.stats.pop(_QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_PASSED, None) + terminalreporter.stats.pop(_QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_FAILED, None) + terminalreporter.stats.pop(_QUARANTINE_ATR_RETRY_OUTCOMES.ATR_ATTEMPT_SKIPPED, None) + terminalreporter.stats.pop(_QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_PASSED, []) + terminalreporter.stats.pop(_QUARANTINE_ATR_RETRY_OUTCOMES.ATR_FINAL_FAILED, []) + + # TODO: report list of fully failed quarantined tests, possibly inside the ATR report. diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index f1da8d2db11..cfcd109f7f9 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -76,11 +76,15 @@ from ddtrace.contrib.pytest._atr_utils import atr_get_teststatus from ddtrace.contrib.pytest._atr_utils import atr_handle_retries from ddtrace.contrib.pytest._atr_utils import atr_pytest_terminal_summary_post_yield + from ddtrace.contrib.pytest._atr_utils import quarantine_atr_get_teststatus + from ddtrace.contrib.pytest._atr_utils import quarantine_pytest_terminal_summary_post_yield log = get_logger(__name__) _NODEID_REGEX = re.compile("^((?P.*)/(?P[^/]*?))::(?P.*?)$") +USER_PROPERTY_QUARANTINED = "dd_quarantined" +OUTCOME_QUARANTINED = "quarantined" def _handle_itr_should_skip(item, test_id) -> bool: @@ -327,6 +331,11 @@ def _pytest_runtest_protocol_pre_yield(item) -> t.Optional[ModuleCodeCollector.C collect_test_coverage = InternalTestSession.should_collect_coverage() and not item_will_skip + is_quarantined = InternalTest.is_quarantined_test(test_id) + if is_quarantined: + # We add this information to user_properties to have it available in pytest_runtest_makereport(). + item.user_properties += [(USER_PROPERTY_QUARANTINED, True)] + if collect_test_coverage: return _start_collecting_coverage() @@ -457,6 +466,8 @@ def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome test_id = _get_test_id_from_item(item) + is_quarantined = InternalTest.is_quarantined_test(test_id) + test_outcome = _process_result(item, call, original_result) # A None value for test_outcome.status implies the test has not finished yet @@ -472,6 +483,11 @@ def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome if not InternalTest.is_finished(test_id): InternalTest.finish(test_id, test_outcome.status, test_outcome.skip_reason, test_outcome.exc_info) + if original_result.failed and is_quarantined: + # Ensure test doesn't count as failed for pytest's exit status logic + # (see ). + original_result.outcome = OUTCOME_QUARANTINED + # ATR and EFD retry tests only if their teardown succeeded to ensure the best chance the retry will succeed # NOTE: this mutates the original result's outcome if InternalTest.stash_get(test_id, "setup_failed") or InternalTest.stash_get(test_id, "teardown_failed"): @@ -480,7 +496,7 @@ def _pytest_runtest_makereport(item: pytest.Item, call: pytest_CallInfo, outcome if InternalTestSession.efd_enabled() and InternalTest.efd_should_retry(test_id): return efd_handle_retries(test_id, item, call.when, original_result, test_outcome) if InternalTestSession.atr_is_enabled() and InternalTest.atr_should_retry(test_id): - return atr_handle_retries(test_id, item, call.when, original_result, test_outcome) + return atr_handle_retries(test_id, item, call.when, original_result, test_outcome, is_quarantined) @pytest.hookimpl(hookwrapper=True) @@ -538,6 +554,9 @@ def _pytest_terminal_summary_post_yield(terminalreporter, failed_reports_initial if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): atr_pytest_terminal_summary_post_yield(terminalreporter) + + quarantine_pytest_terminal_summary_post_yield(terminalreporter) + return @@ -577,7 +596,7 @@ def _pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: if InternalTestSession.efd_enabled() and InternalTestSession.efd_has_failed_tests(): session.exitstatus = pytest.ExitCode.TESTS_FAILED - if InternalTestSession.atr_has_failed_tests() and InternalTestSession.atr_has_failed_tests(): + if InternalTestSession.atr_is_enabled() and InternalTestSession.atr_has_failed_tests(): session.exitstatus = pytest.ExitCode.TESTS_FAILED invoked_by_coverage_run_status = _is_coverage_invoked_by_coverage_run() @@ -615,7 +634,7 @@ def pytest_report_teststatus( return if _pytest_version_supports_atr() and InternalTestSession.atr_is_enabled(): - test_status = atr_get_teststatus(report) + test_status = atr_get_teststatus(report) or quarantine_atr_get_teststatus(report) if test_status is not None: return test_status @@ -624,6 +643,16 @@ def pytest_report_teststatus( if test_status is not None: return test_status + user_properties = getattr(report, "user_properties", []) + is_quarantined = (USER_PROPERTY_QUARANTINED, True) in user_properties + if is_quarantined: + if report.when == "teardown": + return (OUTCOME_QUARANTINED, "q", ("QUARANTINED", {"blue": True})) + else: + # Don't show anything for setup and call of quarantined tests, regardless of + # whether there were errors or not. + return ("", "", "") + @pytest.hookimpl(trylast=True) def pytest_ddtrace_get_item_module_name(item): diff --git a/ddtrace/contrib/pytest/_retry_utils.py b/ddtrace/contrib/pytest/_retry_utils.py index de68e7b7c51..6e38a2974c8 100644 --- a/ddtrace/contrib/pytest/_retry_utils.py +++ b/ddtrace/contrib/pytest/_retry_utils.py @@ -52,6 +52,9 @@ def _get_outcome_from_retry( # _initrequest() needs to be called first because the test has already executed once item._initrequest() + # Reset output capture across retries. + item._report_sections = [] + # Setup setup_call, setup_report = _retry_run_when(item, "setup", outcomes) if setup_report.outcome == outcomes.FAILED: @@ -80,7 +83,6 @@ def _get_outcome_from_retry( _outcome_status = TestStatus.SKIP elif call_report.outcome == outcomes.PASSED: _outcome_status = TestStatus.PASS - # Teardown does not happen if setup skipped if not setup_report.skipped: teardown_call, teardown_report = _retry_run_when(item, "teardown", outcomes) diff --git a/ddtrace/internal/ci_visibility/_api_client.py b/ddtrace/internal/ci_visibility/_api_client.py index aaeaa59f1d7..c69e00793a2 100644 --- a/ddtrace/internal/ci_visibility/_api_client.py +++ b/ddtrace/internal/ci_visibility/_api_client.py @@ -4,6 +4,7 @@ from http.client import RemoteDisconnected import json from json import JSONDecodeError +import os import socket import typing as t from uuid import uuid4 @@ -38,6 +39,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from ddtrace.internal.test_visibility.coverage_lines import CoverageLines +from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.http import ConnectionType from ddtrace.internal.utils.http import Response from ddtrace.internal.utils.http import get_connection @@ -87,6 +89,11 @@ class EarlyFlakeDetectionSettings: faulty_session_threshold: int = 30 +@dataclasses.dataclass(frozen=True) +class QuarantineSettings: + enabled: bool = False + + @dataclasses.dataclass(frozen=True) class TestVisibilityAPISettings: __test__ = False @@ -96,6 +103,7 @@ class TestVisibilityAPISettings: itr_enabled: bool = False flaky_test_retries_enabled: bool = False early_flake_detection: EarlyFlakeDetectionSettings = dataclasses.field(default_factory=EarlyFlakeDetectionSettings) + quarantine: QuarantineSettings = dataclasses.field(default_factory=QuarantineSettings) @dataclasses.dataclass(frozen=True) @@ -359,7 +367,9 @@ def fetch_settings(self) -> TestVisibilityAPISettings: skipping_enabled = attributes["tests_skipping"] require_git = attributes["require_git"] itr_enabled = attributes["itr_enabled"] - flaky_test_retries_enabled = attributes["flaky_test_retries_enabled"] + flaky_test_retries_enabled = attributes["flaky_test_retries_enabled"] or asbool( + os.getenv("_DD_TEST_FORCE_ENABLE_ATR") + ) if attributes["early_flake_detection"]["enabled"]: early_flake_detection = EarlyFlakeDetectionSettings( @@ -372,6 +382,12 @@ def fetch_settings(self) -> TestVisibilityAPISettings: ) else: early_flake_detection = EarlyFlakeDetectionSettings() + + quarantine = QuarantineSettings( + enabled=attributes.get("quarantine", {}).get("enabled", False) + or asbool(os.getenv("_DD_TEST_FORCE_ENABLE_QUARANTINE")) + ) + except KeyError: record_api_request_error(metric_names.error, ERROR_TYPES.UNKNOWN) raise @@ -383,6 +399,7 @@ def fetch_settings(self) -> TestVisibilityAPISettings: itr_enabled=itr_enabled, flaky_test_retries_enabled=flaky_test_retries_enabled, early_flake_detection=early_flake_detection, + quarantine=quarantine, ) record_settings_response( @@ -392,6 +409,7 @@ def fetch_settings(self) -> TestVisibilityAPISettings: itr_enabled=api_settings.itr_enabled, flaky_test_retries_enabled=api_settings.flaky_test_retries_enabled, early_flake_detection_enabled=api_settings.early_flake_detection.enabled, + quarantine_enabled=api_settings.quarantine.enabled, ) return api_settings diff --git a/ddtrace/internal/ci_visibility/api/_base.py b/ddtrace/internal/ci_visibility/api/_base.py index f1e2cd2b3b0..1e56d6b00b6 100644 --- a/ddtrace/internal/ci_visibility/api/_base.py +++ b/ddtrace/internal/ci_visibility/api/_base.py @@ -25,6 +25,7 @@ from ddtrace.ext.test_visibility.api import TestSourceFileInfo from ddtrace.ext.test_visibility.api import TestStatus from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings +from ddtrace.internal.ci_visibility._api_client import QuarantineSettings from ddtrace.internal.ci_visibility.api._coverage_data import TestVisibilityCoverageData from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME from ddtrace.internal.ci_visibility.constants import EVENT_TYPE @@ -71,6 +72,7 @@ class TestVisibilitySessionSettings: coverage_enabled: bool = False efd_settings: EarlyFlakeDetectionSettings = dataclasses.field(default_factory=EarlyFlakeDetectionSettings) atr_settings: AutoTestRetriesSettings = dataclasses.field(default_factory=AutoTestRetriesSettings) + quarantine_settings: QuarantineSettings = dataclasses.field(default_factory=QuarantineSettings) def __post_init__(self): if not isinstance(self.tracer, Tracer): @@ -207,6 +209,12 @@ def _finish_span(self, override_finish_time: Optional[float] = None) -> None: if self._session_settings.atr_settings is not None and self._session_settings.atr_settings.enabled: self._set_atr_tags() + if ( + self._session_settings.quarantine_settings is not None + and self._session_settings.quarantine_settings.enabled + ): + self._set_quarantine_tags() + # Allow items to potentially overwrite default and hierarchy tags. self._set_item_tags() self._set_span_tags() @@ -272,6 +280,10 @@ def _set_atr_tags(self) -> None: """ATR tags are only set at the test level""" pass + def _set_quarantine_tags(self) -> None: + """Quarantine tags are only set at the test or session level""" + pass + def _set_span_tags(self): """This is effectively a callback method for exceptional cases where the item span needs to be modified directly by the class diff --git a/ddtrace/internal/ci_visibility/api/_session.py b/ddtrace/internal/ci_visibility/api/_session.py index 5267a345c0a..44f8aef38f5 100644 --- a/ddtrace/internal/ci_visibility/api/_session.py +++ b/ddtrace/internal/ci_visibility/api/_session.py @@ -15,6 +15,7 @@ from ddtrace.internal.ci_visibility.constants import TEST from ddtrace.internal.ci_visibility.constants import TEST_EFD_ABORT_REASON from ddtrace.internal.ci_visibility.constants import TEST_EFD_ENABLED +from ddtrace.internal.ci_visibility.constants import TEST_SESSION_QUARANTINE_ENABLED from ddtrace.internal.ci_visibility.telemetry.constants import EVENT_TYPES from ddtrace.internal.ci_visibility.telemetry.events import record_event_created from ddtrace.internal.ci_visibility.telemetry.events import record_event_finished @@ -71,6 +72,9 @@ def _set_efd_tags(self): elif self.efd_is_faulty_session(): self.set_tag(TEST_EFD_ABORT_REASON, "faulty") + def _set_quarantine_tags(self): + self.set_tag(TEST_SESSION_QUARANTINE_ENABLED, True) + def _set_itr_tags(self, itr_enabled: bool) -> None: """Set session-level tags based in ITR enablement status""" super()._set_itr_tags(itr_enabled) @@ -178,6 +182,8 @@ def atr_has_failed_tests(self): for _module in self._children.values(): for _suite in _module._children.values(): for _test in _suite._children.values(): + if _test.is_quarantined(): + continue if _test.atr_has_retries() and _test.atr_get_final_status() == TestStatus.FAIL: return True return False diff --git a/ddtrace/internal/ci_visibility/api/_suite.py b/ddtrace/internal/ci_visibility/api/_suite.py index 2eca7316cd0..ba24f82c05c 100644 --- a/ddtrace/internal/ci_visibility/api/_suite.py +++ b/ddtrace/internal/ci_visibility/api/_suite.py @@ -57,7 +57,7 @@ def finish( super().finish(force=force, override_status=override_status, override_finish_time=override_finish_time) def finish_itr_skipped(self) -> None: - """Suites should only count themselves as ITR-skipped of all children are ITR skipped""" + """Suites should only count themselves as ITR-skipped if all children are ITR skipped""" log.debug("Finishing CI Visibility suite %s as ITR skipped", self) for child in self._children.values(): if not (child.is_finished() and child.is_itr_skipped()): diff --git a/ddtrace/internal/ci_visibility/api/_test.py b/ddtrace/internal/ci_visibility/api/_test.py index c63d9753eb8..0f8a2efd41d 100644 --- a/ddtrace/internal/ci_visibility/api/_test.py +++ b/ddtrace/internal/ci_visibility/api/_test.py @@ -21,7 +21,9 @@ from ddtrace.internal.ci_visibility.constants import BENCHMARK from ddtrace.internal.ci_visibility.constants import TEST from ddtrace.internal.ci_visibility.constants import TEST_EFD_ABORT_REASON +from ddtrace.internal.ci_visibility.constants import TEST_HAS_FAILED_ALL_RETRIES from ddtrace.internal.ci_visibility.constants import TEST_IS_NEW +from ddtrace.internal.ci_visibility.constants import TEST_IS_QUARANTINED from ddtrace.internal.ci_visibility.constants import TEST_IS_RETRY from ddtrace.internal.ci_visibility.telemetry.constants import EVENT_TYPES from ddtrace.internal.ci_visibility.telemetry.events import record_event_created_test @@ -55,6 +57,7 @@ def __init__( is_atr_retry: bool = False, resource: Optional[str] = None, is_new: bool = False, + is_quarantined: bool = False, ): self._parameters = parameters super().__init__( @@ -74,6 +77,7 @@ def __init__( self.set_tag(test.PARAMETERS, parameters) self._is_new = is_new + self._is_quarantined = is_quarantined self._efd_is_retry = is_efd_retry self._efd_retries: List[TestVisibilityTest] = [] @@ -126,6 +130,10 @@ def _set_atr_tags(self) -> None: if self._atr_is_retry: self.set_tag(TEST_IS_RETRY, self._atr_is_retry) + def _set_quarantine_tags(self) -> None: + if self._is_quarantined: + self.set_tag(TEST_IS_QUARANTINED, self._is_quarantined) + def _set_span_tags(self) -> None: """This handles setting tags that can't be properly stored in self._tags @@ -149,6 +157,7 @@ def _telemetry_record_event_finished(self): is_new=self.is_new(), is_retry=self._efd_is_retry or self._atr_is_retry, early_flake_detection_abort_reason=self._efd_abort_reason, + is_quarantined=self.is_quarantined(), is_rum=self._is_rum(), browser_driver=self._get_browser_driver(), ) @@ -236,6 +245,11 @@ def is_new(self): # decisions) return self._is_new and (self._parameters is None) + def is_quarantined(self): + return self._session_settings.quarantine_settings.enabled and ( + self._is_quarantined or self.get_tag(TEST_IS_QUARANTINED) + ) + # # EFD (Early Flake Detection) functionality # @@ -360,6 +374,7 @@ def _atr_make_retry_test(self): codeowners=self._codeowners, source_file_info=self._source_file_info, initial_tags=self._tags, + is_quarantined=self.is_quarantined(), is_atr_retry=True, ) retry_test.parent = self.parent @@ -406,7 +421,13 @@ def atr_start_retry(self, retry_number: int): self._atr_get_retry_test(retry_number).start() def atr_finish_retry(self, retry_number: int, status: TestStatus, exc_info: Optional[TestExcInfo] = None): - self._atr_get_retry_test(retry_number).finish_test(status, exc_info=exc_info) + retry_test = self._atr_get_retry_test(retry_number) + + if retry_number >= self._session_settings.atr_settings.max_retries: + if self.atr_get_final_status() == TestStatus.FAIL and self.is_quarantined(): + retry_test.set_tag(TEST_HAS_FAILED_ALL_RETRIES, True) + + retry_test.finish_test(status, exc_info=exc_info) def atr_get_final_status(self) -> TestStatus: if self._status in [TestStatus.PASS, TestStatus.SKIP]: diff --git a/ddtrace/internal/ci_visibility/constants.py b/ddtrace/internal/ci_visibility/constants.py index 7ace37b9424..5a14111bc3b 100644 --- a/ddtrace/internal/ci_visibility/constants.py +++ b/ddtrace/internal/ci_visibility/constants.py @@ -48,6 +48,7 @@ SETTING_ENDPOINT = "/api/v2/libraries/tests/services/setting" SKIPPABLE_ENDPOINT = "/api/v2/ci/tests/skippable" UNIQUE_TESTS_ENDPOINT = "/api/v2/ci/libraries/tests" +DETAILED_TESTS_ENDPOINT = "/api/v2/ci/libraries/tests/detailed" # Intelligent Test Runner constants ITR_UNSKIPPABLE_REASON = "datadog_itr_unskippable" @@ -82,5 +83,9 @@ class REQUESTS_MODE(IntEnum): # EFD and auto retries TEST_IS_NEW = "test.is_new" TEST_IS_RETRY = "test.is_retry" +TEST_IS_QUARANTINED = "test.quarantine.is_quarantined" TEST_EFD_ABORT_REASON = "test.early_flake.abort_reason" TEST_EFD_ENABLED = "test.early_flake.enabled" +TEST_HAS_FAILED_ALL_RETRIES = "test.has_failed_all_retries" + +TEST_SESSION_QUARANTINE_ENABLED = "test_session.quarantine.enabled" diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 0046c21be15..eb8f00d5405 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -39,6 +39,7 @@ from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings from ddtrace.internal.ci_visibility._api_client import EVPProxyTestVisibilityAPIClient from ddtrace.internal.ci_visibility._api_client import ITRData +from ddtrace.internal.ci_visibility._api_client import QuarantineSettings from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.ci_visibility._api_client import _TestVisibilityAPIClientBase from ddtrace.internal.ci_visibility.api._module import TestVisibilityModule @@ -446,6 +447,14 @@ def is_atr_enabled(cls): os.getenv("DD_CIVISIBILITY_FLAKY_RETRY_ENABLED", default=True) ) + @classmethod + def is_quarantine_enabled(cls): + if cls._instance is None: + return False + return cls._instance._api_settings.quarantine.enabled and asbool( + os.getenv("DD_TEST_QUARANTINE_ENABLED", default=True) + ) + @classmethod def should_collect_coverage(cls): return cls._instance._api_settings.coverage_enabled or asbool( @@ -546,11 +555,13 @@ def enable(cls, tracer=None, config=None, service=None): "Final settings: coverage collection: %s, " "test skipping: %s, " "Early Flake Detection: %s, " - "Auto Test Retries: %s", + "Auto Test Retries: %s, " + "Quarantine: %s", cls._instance._collect_coverage_enabled, CIVisibility.test_skipping_enabled(), CIVisibility.is_efd_enabled(), CIVisibility.is_atr_enabled(), + CIVisibility.is_quarantine_enabled(), ) @classmethod @@ -821,6 +832,17 @@ def get_atr_api_settings(cls) -> Optional[AutoTestRetriesSettings]: return None + @classmethod + def get_quarantine_api_settings(cls) -> Optional[QuarantineSettings]: + if not cls.enabled: + error_msg = "CI Visibility is not enabled" + log.warning(error_msg) + raise CIVisibilityError(error_msg) + instance = cls.get_instance() + if instance is None or instance._api_settings is None: + return None + return instance._api_settings.quarantine + @classmethod def get_workspace_path(cls) -> Optional[str]: if not cls.enabled: @@ -900,6 +922,15 @@ def is_unique_test(cls, test_id: Union[TestId, InternalTestId]) -> bool: return test_id in instance._unique_test_ids + @classmethod + def is_quarantined(cls, test_id: Union[TestId, InternalTestId]) -> bool: + instance = cls.get_instance() + if instance is None: + return False + + # TODO: retrieve this information from the API, once it is available in the backend. + return False + def _requires_civisibility_enabled(func): def wrapper(*args, **kwargs): @@ -939,6 +970,10 @@ def _on_discover_session(discover_args: TestSession.DiscoverArgs): if atr_api_settings is None or not CIVisibility.is_atr_enabled(): atr_api_settings = AutoTestRetriesSettings() + quarantine_api_settings = CIVisibility.get_quarantine_api_settings() + if quarantine_api_settings is None or not CIVisibility.is_quarantine_enabled(): + quarantine_api_settings = QuarantineSettings() + session_settings = TestVisibilitySessionSettings( tracer=tracer, test_service=test_service, @@ -960,6 +995,7 @@ def _on_discover_session(discover_args: TestSession.DiscoverArgs): coverage_enabled=CIVisibility.should_collect_coverage(), efd_settings=efd_api_settings, atr_settings=atr_api_settings, + quarantine_settings=quarantine_api_settings, ) session = TestVisibilitySession( @@ -1150,6 +1186,11 @@ def _on_discover_test(discover_args: Test.DiscoverArgs): else: is_new = False + if CIVisibility.is_quarantine_enabled(): + is_quarantined = CIVisibility.is_quarantined(discover_args.test_id) + else: + is_quarantined = False + suite.add_child( discover_args.test_id, TestVisibilityTest( @@ -1160,6 +1201,7 @@ def _on_discover_test(discover_args: Test.DiscoverArgs): source_file_info=discover_args.source_file_info, resource=discover_args.resource, is_new=is_new, + is_quarantined=is_quarantined, ), ) @@ -1170,6 +1212,12 @@ def _on_is_new_test(test_id: Union[TestId, InternalTestId]) -> bool: return CIVisibility.get_test_by_id(test_id).is_new() +@_requires_civisibility_enabled +def _on_is_quarantined_test(test_id: Union[TestId, InternalTestId]) -> bool: + log.debug("Handling is quarantined test for test %s", test_id) + return CIVisibility.get_test_by_id(test_id).is_quarantined() + + @_requires_civisibility_enabled def _on_start_test(test_id: TestId): log.debug("Handling start for test id %s", test_id) @@ -1215,6 +1263,7 @@ def _register_test_handlers(): log.debug("Registering test handlers") core.on("test_visibility.test.discover", _on_discover_test) core.on("test_visibility.test.is_new", _on_is_new_test, "is_new") + core.on("test_visibility.test.is_quarantined", _on_is_quarantined_test, "is_quarantined") core.on("test_visibility.test.start", _on_start_test) core.on("test_visibility.test.finish", _on_finish_test) core.on("test_visibility.test.set_parameters", _on_set_test_parameters) diff --git a/ddtrace/internal/ci_visibility/telemetry/events.py b/ddtrace/internal/ci_visibility/telemetry/events.py index be39c8079cf..34c603c3b03 100644 --- a/ddtrace/internal/ci_visibility/telemetry/events.py +++ b/ddtrace/internal/ci_visibility/telemetry/events.py @@ -50,7 +50,6 @@ def _record_event( log.debug("has_codeowners tag can only be set for sessions, but event type is %s", event_type) if is_unsupported_ci and event_type != EVENT_TYPES.SESSION: log.debug("unsupported_ci tag can only be set for sessions, but event type is %s", event_type) - if early_flake_detection_abort_reason and ( event_type not in [EVENT_TYPES.SESSION] or event != EVENTS_TELEMETRY.FINISHED ): @@ -151,6 +150,7 @@ def record_event_finished_test( is_rum: bool = False, browser_driver: Optional[str] = None, is_benchmark: bool = False, + is_quarantined: bool = False, ): log.debug( "Recording test event finished: test_framework=%s" @@ -159,7 +159,8 @@ def record_event_finished_test( ", early_flake_detection_abort_reason=%s" ", is_rum=%s" ", browser_driver=%s" - ", is_benchmark=%s", + ", is_benchmark=%s" + ", is_quarantined=%s", test_framework, is_new, is_retry, @@ -167,6 +168,7 @@ def record_event_finished_test( is_rum, browser_driver, is_benchmark, + is_quarantined, ) tags: List[Tuple[str, str]] = [("event_type", EVENT_TYPES.TEST)] @@ -185,5 +187,7 @@ def record_event_finished_test( tags.append(("browser_driver", browser_driver)) if early_flake_detection_abort_reason is not None: tags.append(("early_flake_detection_abort_reason", early_flake_detection_abort_reason)) + if is_quarantined: + tags.append(("is_quarantined", "true")) telemetry_writer.add_count_metric(_NAMESPACE, EVENTS_TELEMETRY.FINISHED, 1, tuple(tags)) diff --git a/ddtrace/internal/ci_visibility/telemetry/git.py b/ddtrace/internal/ci_visibility/telemetry/git.py index 761ecc62a00..faf01621cde 100644 --- a/ddtrace/internal/ci_visibility/telemetry/git.py +++ b/ddtrace/internal/ci_visibility/telemetry/git.py @@ -52,6 +52,7 @@ def record_settings_response( itr_enabled: Optional[bool] = False, flaky_test_retries_enabled: Optional[bool] = False, early_flake_detection_enabled: Optional[bool] = False, + quarantine_enabled: Optional[bool] = False, ) -> None: log.debug( "Recording settings telemetry:" @@ -82,6 +83,8 @@ def record_settings_response( response_tags.append(("flaky_test_retries_enabled", "true")) if early_flake_detection_enabled: response_tags.append(("early_flake_detection_enabled", "true")) + if quarantine_enabled: + response_tags.append(("quarantine_enabled", "true")) if response_tags: telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_RESPONSE, 1, tuple(response_tags)) diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index 84f559a4701..c4e25b29e06 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -175,6 +175,16 @@ def is_new_test(item_id: InternalTestId) -> bool: log.debug("Test %s is new: %s", item_id, is_new) return is_new + @staticmethod + @_catch_and_log_exceptions + def is_quarantined_test(item_id: InternalTestId) -> bool: + log.debug("Checking if test %s is quarantined", item_id) + is_quarantined = bool( + core.dispatch_with_results("test_visibility.test.is_quarantined", (item_id,)).is_quarantined.value + ) + log.debug("Test %s is quarantined: %s", item_id, is_quarantined) + return is_quarantined + class OverwriteAttributesArgs(NamedTuple): test_id: InternalTestId name: t.Optional[str] = None diff --git a/tests/ci_visibility/api_client/_util.py b/tests/ci_visibility/api_client/_util.py index 8a260fbf3e6..403482688a9 100644 --- a/tests/ci_visibility/api_client/_util.py +++ b/tests/ci_visibility/api_client/_util.py @@ -105,6 +105,23 @@ def _get_tests_api_response(tests_body: t.Optional[t.Dict] = None): return Response(200, json.dumps(response)) +def _get_detailed_tests_api_response(modules: t.Dict): + response = {"data": {"id": "J0ucvcSApX8", "type": "ci_app_libraries_tests", "attributes": {"modules": []}}} + + for module_id, suites in modules.items(): + module = {"id": module_id, "suites": []} + response["data"]["attributes"]["modules"].append(module) + + for suite_id, tests in suites.items(): + suite = {"id": suite_id, "tests": []} + module["suites"].append(suite) + + for test_id in tests: + suite["tests"].append({"id": test_id}) + + return Response(200, json.dumps(response)) + + def _make_fqdn_internal_test_id(module_name: str, suite_name: str, test_name: str, parameters: t.Optional[str] = None): """An easy way to create a test id "from the bottom up" diff --git a/tests/ci_visibility/test_encoder.py b/tests/ci_visibility/test_encoder.py index 402668a624a..4a1d0fe2331 100644 --- a/tests/ci_visibility/test_encoder.py +++ b/tests/ci_visibility/test_encoder.py @@ -1,15 +1,9 @@ import json import os -from unittest import mock import msgpack -import pytest -import ddtrace from ddtrace._trace.span import Span -from ddtrace.contrib.pytest.plugin import is_enabled -from ddtrace.internal.ci_visibility import CIVisibility -from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME from ddtrace.internal.ci_visibility.constants import ITR_CORRELATION_ID_TAG_NAME from ddtrace.internal.ci_visibility.constants import SESSION_ID @@ -17,10 +11,7 @@ from ddtrace.internal.ci_visibility.encoder import CIVisibilityCoverageEncoderV02 from ddtrace.internal.ci_visibility.encoder import CIVisibilityEncoderV01 from ddtrace.internal.encoding import JSONEncoder -from tests.ci_visibility.test_ci_visibility import _dummy_noop_git_client -from tests.ci_visibility.util import _patch_dummy_writer -from tests.utils import TracerTestCase -from tests.utils import override_env +from tests.contrib.pytest.test_pytest import PytestTestCaseBase def test_encode_traces_civisibility_v0(): @@ -241,38 +232,7 @@ def test_encode_traces_civisibility_v2_coverage_empty_traces(): assert complete_payload is None -class PytestEncodingTestCase(TracerTestCase): - @pytest.fixture(autouse=True) - def fixtures(self, testdir, monkeypatch): - self.testdir = testdir - self.monkeypatch = monkeypatch - - def inline_run(self, *args): - """Execute test script with test tracer.""" - - class CIVisibilityPlugin: - @staticmethod - def pytest_configure(config): - if is_enabled(config): - with _patch_dummy_writer(): - assert CIVisibility.enabled - CIVisibility.disable() - CIVisibility.enable(tracer=self.tracer, config=ddtrace.config.pytest) - - with override_env(dict(DD_API_KEY="foobar.baz")), _dummy_noop_git_client(), mock.patch( - "ddtrace.internal.ci_visibility._api_client._TestVisibilityAPIClientBase.fetch_settings", - return_value=TestVisibilityAPISettings(False, False, False, False), - ): - return self.testdir.inline_run(*args, plugins=[CIVisibilityPlugin()]) - - def subprocess_run(self, *args): - """Execute test script with test tracer.""" - return self.testdir.runpytest_subprocess(*args) - - def teardown(self): - if CIVisibility.enabled: - CIVisibility.disable() - +class PytestEncodingTestCase(PytestTestCaseBase): def test_event_payload(self): """Test that a pytest test case will generate a test event, but with: - test_session_id, test_module_id, test_suite_id moved from meta to event content dictionary diff --git a/tests/ci_visibility/test_quarantine.py b/tests/ci_visibility/test_quarantine.py new file mode 100644 index 00000000000..b467431d074 --- /dev/null +++ b/tests/ci_visibility/test_quarantine.py @@ -0,0 +1,56 @@ +from pathlib import Path +from unittest import mock + +from ddtrace.ext.test_visibility.api import TestStatus +from ddtrace.internal.ci_visibility._api_client import EarlyFlakeDetectionSettings +from ddtrace.internal.ci_visibility._api_client import QuarantineSettings +from ddtrace.internal.ci_visibility.api._base import TestVisibilitySessionSettings +from ddtrace.internal.ci_visibility.api._session import TestVisibilitySession +from ddtrace.internal.ci_visibility.api._test import TestVisibilityTest +from ddtrace.internal.ci_visibility.telemetry.constants import TEST_FRAMEWORKS +from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings +from tests.utils import DummyTracer + + +class TestCIVisibilityTestQuarantine: + """Tests that the classes in the CIVisibility API correctly handle quarantine.""" + + def _get_session_settings( + self, + ) -> TestVisibilitySessionSettings: + return TestVisibilitySessionSettings( + tracer=DummyTracer(), + test_service="qurantine_test_service", + test_command="qurantine_test_command", + test_framework="qurantine_test_framework", + test_framework_metric_name=TEST_FRAMEWORKS.MANUAL, + test_framework_version="0.0", + session_operation_name="qurantine_session", + module_operation_name="qurantine_module", + suite_operation_name="qurantine_suite", + test_operation_name="qurantine_test", + workspace_path=Path().absolute(), + efd_settings=EarlyFlakeDetectionSettings(enabled=False), + atr_settings=AutoTestRetriesSettings(enabled=False), + quarantine_settings=QuarantineSettings(enabled=True), + ) + + def test_quarantine_tags_set(self): + session = TestVisibilitySession( + session_settings=self._get_session_settings(), + ) + + test = TestVisibilityTest( + name="quarantine_test_1", + session_settings=session._session_settings, + is_quarantined=True, + ) + + with mock.patch.object(test, "get_session", return_value=session): + session.start() + test.start() + test.finish_test(TestStatus.FAIL) + session.finish() + + assert test._span.get_tag("test.quarantine.is_quarantined") == "true" + assert session._span.get_tag("test_session.quarantine.enabled") == "true" diff --git a/tests/contrib/pytest/test_pytest_atr.py b/tests/contrib/pytest/test_pytest_atr.py index 742a4f220d0..3e526e8cdf7 100644 --- a/tests/contrib/pytest/test_pytest_atr.py +++ b/tests/contrib/pytest/test_pytest_atr.py @@ -97,10 +97,6 @@ def set_up_atr(self): return_value=TestVisibilityAPISettings(flaky_test_retries_enabled=True), ): yield - from ddtrace.internal.ci_visibility.recorder import CIVisibility - - if CIVisibility.enabled: - CIVisibility.disable() def test_pytest_atr_no_ddtrace_does_not_retry(self): self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) diff --git a/tests/contrib/pytest/test_pytest_efd.py b/tests/contrib/pytest/test_pytest_efd.py index f08e170d089..2affcec3585 100644 --- a/tests/contrib/pytest/test_pytest_efd.py +++ b/tests/contrib/pytest/test_pytest_efd.py @@ -116,10 +116,6 @@ def set_up_efd(self): ), ): yield - from ddtrace.internal.ci_visibility.recorder import CIVisibility - - if CIVisibility.enabled: - CIVisibility.disable() def test_pytest_efd_no_ddtrace_does_not_retry(self): self.testdir.makepyfile(test_known_pass=_TEST_KNOWN_PASS_CONTENT) diff --git a/tests/contrib/pytest/test_pytest_quarantine.py b/tests/contrib/pytest/test_pytest_quarantine.py new file mode 100644 index 00000000000..93e7fe5dbde --- /dev/null +++ b/tests/contrib/pytest/test_pytest_quarantine.py @@ -0,0 +1,294 @@ +"""Tests Early Flake Detection (EFD) functionality + +The tests in this module only validate the behavior of EFD, so only counts and statuses of tests, retries, and sessions +are checked. + +- The same known tests are used to override fetching of known tests. +- The session object is patched to never be a faulty session, by default. +""" +from unittest import mock + +import pytest + +from ddtrace.contrib.pytest._utils import _USE_PLUGIN_V2 +from ddtrace.contrib.pytest._utils import _pytest_version_supports_efd +from ddtrace.internal.ci_visibility._api_client import QuarantineSettings +from ddtrace.internal.ci_visibility._api_client import TestVisibilityAPISettings +from tests.contrib.pytest.test_pytest import PytestTestCaseBase +from tests.contrib.pytest.test_pytest import _get_spans_from_list + + +pytestmark = pytest.mark.skipif( + not (_USE_PLUGIN_V2 and _pytest_version_supports_efd()), + reason="Quarantine requires v2 of the plugin and pytest >=7.0", +) + +_TEST_PASS_QUARANTINED = """ +import pytest + +@pytest.mark.dd_tags(**{"test.quarantine.is_quarantined": True}) +def test_pass_quarantined(): + assert True +""" + +_TEST_PASS_UNQUARANTINED = """ +import pytest + +def test_pass_normal(): + assert True +""" + +_TEST_FAIL_QUARANTINED = """ +import pytest + +@pytest.mark.dd_tags(**{"test.quarantine.is_quarantined": True}) +def test_fail_quarantined(): + assert False +""" + +_TEST_FAIL_UNQUARANTINED = """ +import pytest + +def test_fail_normal(): + assert False +""" + +_TEST_FAIL_SETUP_QUARANTINED = """ +import pytest + +@pytest.fixture() +def fail_setup(): + raise ValueError("fail setup") + +@pytest.mark.dd_tags(**{"test.quarantine.is_quarantined": True}) +def test_fail_setup(fail_setup): + assert True +""" + +_TEST_FAIL_TEARDOWN_QUARANTINED = """ +import pytest + +@pytest.fixture() +def fail_teardown(): + yield + raise ValueError("fail teardown") + +@pytest.mark.dd_tags(**{"test.quarantine.is_quarantined": True}) +def test_fail_teardown(fail_teardown): + assert True +""" + + +def assert_stats(rec, **outcomes): + """ + Assert that the correct number of test results of each type is present in a test run. + + This is similar to `rec.assertoutcome()`, but works with test statuses other than 'passed', 'failed' and 'skipped'. + """ + stats = {**rec.getcall("pytest_terminal_summary").terminalreporter.stats} + stats.pop("", None) + + for outcome, expected_count in outcomes.items(): + actual_count = len(stats.pop(outcome, [])) + assert actual_count == expected_count, f"Expected {expected_count} {outcome} tests, got {actual_count}" + + assert not stats, "Found unexpected stats in test results: {', '.join(stats.keys())}" + + +class PytestQuarantineTestCase(PytestTestCaseBase): + @pytest.fixture(autouse=True, scope="function") + def set_up_quarantine(self): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True), + flaky_test_retries_enabled=False, + ), + ): + yield + + def test_fail_quarantined_no_ddtrace_does_not_quarantine(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_pass_normal=_TEST_PASS_UNQUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + self.testdir.makepyfile(test_fail_normal=_TEST_FAIL_UNQUARANTINED) + rec = self.inline_run("-q") + assert rec.ret == 1 + assert_stats(rec, passed=2, failed=2) + assert len(self.pop_spans()) == 0 # ddtrace disabled, not collecting traces + + def test_fail_quarantined_with_ddtrace_does_not_fail_session(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=2) + + assert len(self.pop_spans()) > 0 + + def test_failing_and_passing_quarantined_and_unquarantined_tests(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_pass_normal=_TEST_PASS_UNQUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + self.testdir.makepyfile(test_fail_normal=_TEST_FAIL_UNQUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + assert rec.ret == 1 + assert_stats(rec, quarantined=2, passed=1, failed=1) + + assert len(self.pop_spans()) > 0 + + def test_env_var_disables_quarantine(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q", extra_env={"DD_TEST_QUARANTINE_ENABLED": "0"}) + + assert rec.ret == 1 + assert_stats(rec, quarantined=0, failed=1) + + assert len(self.pop_spans()) > 0 + + def test_env_var_does_not_override_api(self): + """Environment variable works as a kill-switch; if quarantine is disabled in the API, + the env var cannot make it enabled. + """ + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=False), + ), + ): + rec = self.inline_run("--ddtrace", "-q", extra_env={"DD_TEST_QUARANTINE_ENABLED": "1"}) + + assert rec.ret == 1 + assert_stats(rec, failed=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_outcomes_without_atr(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + outcomes = [(call.report.when, call.report.outcome) for call in rec.getcalls("pytest_report_teststatus")] + assert outcomes == [ + ("setup", "passed"), + ("call", "quarantined"), + ("teardown", "passed"), + ] + + def test_quarantine_outcomes_with_atr(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True), + flaky_test_retries_enabled=True, + ), + ): + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + outcomes = [(call.report.when, call.report.outcome) for call in rec.getcalls("pytest_report_teststatus")] + assert outcomes == [ + ("setup", "passed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_attempt_failed"), + ("call", "dd_quarantine_atr_final_failed"), + ("teardown", "passed"), + ] + + def test_quarantine_fail_setup(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_SETUP_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_fail_teardown(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_SETUP_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_spans_without_atr(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=2) + + spans = self.pop_spans() + + [session_span] = _get_spans_from_list(spans, "session") + assert session_span.get_tag("test_session.quarantine.enabled") == "true" + + [module_span] = _get_spans_from_list(spans, "module") + [suite_span_fail_quarantined] = _get_spans_from_list(spans, "suite", "test_fail_quarantined.py") + [suite_span_pass_quarantined] = _get_spans_from_list(spans, "suite", "test_pass_quarantined.py") + + [test_span_fail_quarantined] = _get_spans_from_list(spans, "test", "test_fail_quarantined") + assert test_span_fail_quarantined.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_fail_quarantined.get_tag("test.status") == "fail" + + [test_span_pass_quarantined] = _get_spans_from_list(spans, "test", "test_pass_quarantined") + assert test_span_pass_quarantined.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_pass_quarantined.get_tag("test.status") == "pass" + + def test_quarantine_spans_with_atr(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True), + flaky_test_retries_enabled=True, + ), + ): + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=2) + + spans = self.pop_spans() + + [session_span] = _get_spans_from_list(spans, "session") + assert session_span.get_tag("test_session.quarantine.enabled") == "true" + + [module_span] = _get_spans_from_list(spans, "module") + [suite_span_fail_quarantined] = _get_spans_from_list(spans, "suite", "test_fail_quarantined.py") + [suite_span_pass_quarantined] = _get_spans_from_list(spans, "suite", "test_pass_quarantined.py") + + test_spans_fail_quarantined = _get_spans_from_list(spans, "test", "test_fail_quarantined") + assert len(test_spans_fail_quarantined) == 6 + assert all(span.get_tag("test.quarantine.is_quarantined") == "true" for span in test_spans_fail_quarantined) + assert all(span.get_tag("test.status") == "fail" for span in test_spans_fail_quarantined) + assert test_spans_fail_quarantined[0].get_tag("test.is_retry") is None + assert all(span.get_tag("test.is_retry") for span in test_spans_fail_quarantined[1:]) + + [test_span_pass_quarantined] = _get_spans_from_list(spans, "test", "test_pass_quarantined") + assert test_span_pass_quarantined.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_pass_quarantined.get_tag("test.status") == "pass" From 55c8dd09cab0ec73c70ea875f04058cc11ad344a Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 20 Dec 2024 10:13:24 -0500 Subject: [PATCH 353/372] ci(gevent): improve ddtrace-run test assertion message (#11740) --- tests/contrib/gevent/test_tracer.py | 2 +- tests/contrib/suitespec.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/contrib/gevent/test_tracer.py b/tests/contrib/gevent/test_tracer.py index 9d622ac5d31..a40a0f3cdaf 100644 --- a/tests/contrib/gevent/test_tracer.py +++ b/tests/contrib/gevent/test_tracer.py @@ -411,6 +411,6 @@ def test_ddtracerun(self): p.wait() stdout, stderr = p.stdout.read(), p.stderr.read() - assert p.returncode == 0, stderr.decode() + assert p.returncode == 0, f"stdout: {stdout.decode()}\n\nstderr: {stderr.decode()}" assert b"Test success" in stdout, stdout.decode() assert b"RecursionError" not in stderr, stderr.decode() diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 83a48ea1f48..8c5fbc72da2 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -645,7 +645,7 @@ suites: - '@gevent' - tests/contrib/gevent/* runner: riot - snapshot: true + snapshot: false graphene: paths: - '@bootstrap' From 6ea56c5374dccf3445476538442f4572786fca60 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 20 Dec 2024 10:17:25 -0500 Subject: [PATCH 354/372] chore: remove time module functions from ddtrace.internal.compat (#11799) Looks like these functions were added to support Python < 3.7 and we no longer support those. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- benchmarks/rate_limiter/scenario.py | 3 +- ddtrace/_trace/span.py | 2 +- .../internal/botocore/services/kinesis.py | 2 +- ddtrace/contrib/internal/kafka/patch.py | 2 +- ddtrace/debugging/_debugger.py | 6 +- ddtrace/debugging/_origin/span.py | 6 +- ddtrace/internal/compat.py | 35 ---- ddtrace/internal/opentelemetry/span.py | 2 +- ddtrace/internal/rate_limiter.py | 11 +- ddtrace/internal/utils/time.py | 14 +- ddtrace/profiling/collector/_lock.py | 8 +- ddtrace/profiling/collector/memalloc.py | 4 +- ddtrace/profiling/collector/stack.pyx | 12 +- ddtrace/profiling/scheduler.py | 14 +- ddtrace/vendor/__init__.py | 15 +- ddtrace/vendor/monotonic/__init__.py | 169 ------------------ tests/debugging/mocking.py | 2 +- tests/integration/test_sampling.py | 2 +- tests/profiling/exporter/test_http.py | 5 +- tests/profiling/test_accuracy.py | 10 +- tests/profiling/test_scheduler.py | 6 +- tests/submod/stuff.py | 2 +- tests/tracer/test_rate_limiter.py | 45 ++--- 23 files changed, 80 insertions(+), 297 deletions(-) delete mode 100644 ddtrace/vendor/monotonic/__init__.py diff --git a/benchmarks/rate_limiter/scenario.py b/benchmarks/rate_limiter/scenario.py index 5c1f80f2537..5210647ef89 100644 --- a/benchmarks/rate_limiter/scenario.py +++ b/benchmarks/rate_limiter/scenario.py @@ -9,7 +9,8 @@ class RateLimiter(bm.Scenario): num_windows: int def run(self): - from ddtrace.internal.compat import time_ns + from time import time_ns + from ddtrace.internal.rate_limiter import RateLimiter rate_limiter = RateLimiter(rate_limit=self.rate_limit, time_window=self.time_window) diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index db90a769cd9..afb3496db80 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -1,6 +1,7 @@ import math import pprint import sys +from time import time_ns import traceback from types import TracebackType from typing import Any @@ -46,7 +47,6 @@ from ddtrace.internal.compat import StringIO from ddtrace.internal.compat import ensure_text from ddtrace.internal.compat import is_integer -from ddtrace.internal.compat import time_ns from ddtrace.internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS from ddtrace.internal.constants import SPAN_API_DATADOG from ddtrace.internal.logger import get_logger diff --git a/ddtrace/contrib/internal/botocore/services/kinesis.py b/ddtrace/contrib/internal/botocore/services/kinesis.py index 1f8bbef1478..0287c29d2bc 100644 --- a/ddtrace/contrib/internal/botocore/services/kinesis.py +++ b/ddtrace/contrib/internal/botocore/services/kinesis.py @@ -1,5 +1,6 @@ from datetime import datetime import json +from time import time_ns from typing import Any from typing import Dict from typing import List @@ -12,7 +13,6 @@ from ddtrace.contrib.trace_utils import ext_service from ddtrace.ext import SpanTypes from ddtrace.internal import core -from ddtrace.internal.compat import time_ns from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_cloud_messaging_operation from ddtrace.internal.schema import schematize_service_name diff --git a/ddtrace/contrib/internal/kafka/patch.py b/ddtrace/contrib/internal/kafka/patch.py index b8e8fce007d..339e2469914 100644 --- a/ddtrace/contrib/internal/kafka/patch.py +++ b/ddtrace/contrib/internal/kafka/patch.py @@ -1,5 +1,6 @@ import os import sys +from time import time_ns import confluent_kafka @@ -12,7 +13,6 @@ from ddtrace.ext import SpanTypes from ddtrace.ext import kafka as kafkax from ddtrace.internal import core -from ddtrace.internal.compat import time_ns from ddtrace.internal.constants import COMPONENT from ddtrace.internal.constants import MESSAGING_SYSTEM from ddtrace.internal.logger import get_logger diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index 1c2429ba569..c36bb94fdfb 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -6,6 +6,7 @@ from pathlib import Path import sys import threading +import time from types import CodeType from types import FunctionType from types import ModuleType @@ -43,7 +44,6 @@ from ddtrace.debugging._signal.model import SignalState from ddtrace.debugging._uploader import LogsIntakeUploaderV1 from ddtrace.debugging._uploader import UploaderProduct -from ddtrace.internal import compat from ddtrace.internal.logger import get_logger from ddtrace.internal.metrics import Metrics from ddtrace.internal.module import ModuleHookType @@ -202,11 +202,11 @@ def _open_signals(self) -> None: signals.append(signal) # Save state on the wrapping context - self.set("start_time", compat.monotonic_ns()) + self.set("start_time", time.monotonic_ns()) self.set("signals", signals) def _close_signals(self, retval=None, exc_info=(None, None, None)) -> None: - end_time = compat.monotonic_ns() + end_time = time.monotonic_ns() signals = cast(Deque[Signal], self.get("signals")) while signals: # Open probe signals are ordered, with those that have created new diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index bd3744c20f2..9b592df2bde 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -2,6 +2,7 @@ from itertools import count from pathlib import Path import sys +import time # from threading import current_thread from types import FrameType @@ -24,7 +25,6 @@ # from ddtrace.debugging._signal.snapshot import Snapshot from ddtrace.debugging._signal.model import Signal from ddtrace.ext import EXIT_SPAN_TYPES -from ddtrace.internal import compat from ddtrace.internal import core from ddtrace.internal.packages import is_user_code from ddtrace.internal.safety import _isinstance @@ -170,7 +170,7 @@ def __enter__(self): # span.set_tag_str("_dd.code_origin.frames.0.snapshot_id", snapshot.uuid) # self.set("context", context) - # self.set("start_time", compat.monotonic_ns()) + # self.set("start_time", time.monotonic_ns()) return self @@ -181,7 +181,7 @@ def _close_signal(self, retval=None, exc_info=(None, None, None)): # No snapshot was created return - signal.do_exit(retval, exc_info, compat.monotonic_ns() - self.get("start_time")) + signal.do_exit(retval, exc_info, time.monotonic_ns() - self.get("start_time")) def __return__(self, retval): self._close_signal(retval=retval) diff --git a/ddtrace/internal/compat.py b/ddtrace/internal/compat.py index 457618dc393..7f00043f049 100644 --- a/ddtrace/internal/compat.py +++ b/ddtrace/internal/compat.py @@ -122,41 +122,6 @@ def is_integer(obj): return isinstance(obj, int) and not isinstance(obj, bool) -try: - from time import time_ns -except ImportError: - from time import time as _time - - def time_ns(): - # type: () -> int - return int(_time() * 10e5) * 1000 - - -try: - from time import monotonic -except ImportError: - from ddtrace.vendor.monotonic import monotonic - - -try: - from time import monotonic_ns -except ImportError: - - def monotonic_ns(): - # type: () -> int - return int(monotonic() * 1e9) - - -try: - from time import process_time_ns -except ImportError: - from time import clock as _process_time # type: ignore[attr-defined] - - def process_time_ns(): - # type: () -> int - return int(_process_time() * 1e9) - - main_thread = threading.main_thread() diff --git a/ddtrace/internal/opentelemetry/span.py b/ddtrace/internal/opentelemetry/span.py index bd2b0dfe83b..15f497a358e 100644 --- a/ddtrace/internal/opentelemetry/span.py +++ b/ddtrace/internal/opentelemetry/span.py @@ -1,3 +1,4 @@ +from time import time_ns import traceback from typing import TYPE_CHECKING @@ -15,7 +16,6 @@ from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.internal.compat import time_ns from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import flatten_key_value from ddtrace.internal.utils.formats import is_sequence diff --git a/ddtrace/internal/rate_limiter.py b/ddtrace/internal/rate_limiter.py index 981701cd51b..0a97a6a7abc 100644 --- a/ddtrace/internal/rate_limiter.py +++ b/ddtrace/internal/rate_limiter.py @@ -4,6 +4,7 @@ from dataclasses import field import random import threading +import time from typing import Any # noqa:F401 from typing import Callable # noqa:F401 from typing import Optional # noqa:F401 @@ -11,8 +12,6 @@ from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.vendor.debtcollector import deprecate -from ..internal import compat - class RateLimiter(object): """ @@ -49,7 +48,7 @@ def __init__(self, rate_limit: int, time_window: float = 1e9): self.tokens = rate_limit # type: float self.max_tokens = rate_limit - self.last_update_ns = compat.monotonic_ns() + self.last_update_ns = time.monotonic_ns() self.current_window_ns = 0 # type: float self.tokens_allowed = 0 @@ -77,7 +76,7 @@ def is_allowed(self, timestamp_ns: Optional[int] = None) -> bool: # rate limits are tested and mocked in pytest so we need to compute the timestamp here # (or move the unit tests to rust) - timestamp_ns = timestamp_ns or compat.monotonic_ns() + timestamp_ns = timestamp_ns or time.monotonic_ns() allowed = self._is_allowed(timestamp_ns) # Update counts used to determine effective rate self._update_rate_counts(allowed, timestamp_ns) @@ -213,7 +212,7 @@ class BudgetRateLimiterWithJitter: call_once: bool = False budget: float = field(init=False) max_budget: float = field(init=False) - last_time: float = field(init=False, default_factory=compat.monotonic) + last_time: float = field(init=False, default_factory=time.monotonic) _lock: threading.Lock = field(init=False, default_factory=threading.Lock) def __post_init__(self): @@ -229,7 +228,7 @@ def limit(self, f: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: An """Make rate-limited calls to a function with the given arguments.""" should_call = False with self._lock: - now = compat.monotonic() + now = time.monotonic() self.budget += self.limit_rate * (now - self.last_time) * (0.5 + random.random()) # jitter should_call = self.budget >= 1.0 if self.budget > self.max_budget: diff --git a/ddtrace/internal/utils/time.py b/ddtrace/internal/utils/time.py index 1993b9dd21c..6be4946d803 100644 --- a/ddtrace/internal/utils/time.py +++ b/ddtrace/internal/utils/time.py @@ -1,9 +1,9 @@ from datetime import datetime +import time from types import TracebackType from typing import Optional from typing import Type # noqa:F401 -from ddtrace.internal import compat from ddtrace.internal.logger import get_logger @@ -46,7 +46,7 @@ def __init__(self) -> None: def start(self): # type: () -> StopWatch """Starts the watch.""" - self._started_at = compat.monotonic() + self._started_at = time.monotonic() return self def elapsed(self) -> float: @@ -59,7 +59,7 @@ def elapsed(self) -> float: if self._started_at is None: raise RuntimeError("Can not get the elapsed time of a stopwatch" " if it has not been started/stopped") if self._stopped_at is None: - now = compat.monotonic() + now = time.monotonic() else: now = self._stopped_at return now - self._started_at @@ -81,7 +81,7 @@ def stop(self): """Stops the watch.""" if self._started_at is None: raise RuntimeError("Can not stop a stopwatch that has not been" " started") - self._stopped_at = compat.monotonic() + self._stopped_at = time.monotonic() return self @@ -89,7 +89,7 @@ class HourGlass(object): """An implementation of an hourglass.""" def __init__(self, duration: float) -> None: - t = compat.monotonic() + t = time.monotonic() self._duration = duration self._started_at = t - duration @@ -99,7 +99,7 @@ def __init__(self, duration: float) -> None: def turn(self) -> None: """Turn the hourglass.""" - t = compat.monotonic() + t = time.monotonic() top_0 = self._end_at - self._started_at bottom = self._duration - top_0 + min(t - self._started_at, top_0) @@ -119,7 +119,7 @@ def _trickled(self): def _trickling(self): # type: () -> bool - if compat.monotonic() < self._end_at: + if time.monotonic() < self._end_at: return True # No longer trickling, so we change state diff --git a/ddtrace/profiling/collector/_lock.py b/ddtrace/profiling/collector/_lock.py index 4ee0e692fac..6dedf3295f7 100644 --- a/ddtrace/profiling/collector/_lock.py +++ b/ddtrace/profiling/collector/_lock.py @@ -4,13 +4,13 @@ import abc import os.path import sys +import time import types import typing import wrapt from ddtrace._trace.tracer import Tracer -from ddtrace.internal import compat from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.logger import get_logger from ddtrace.profiling import _threading @@ -117,12 +117,12 @@ def _acquire(self, inner_func, *args, **kwargs): if not self._self_capture_sampler.capture(): return inner_func(*args, **kwargs) - start = compat.monotonic_ns() + start = time.monotonic_ns() try: return inner_func(*args, **kwargs) finally: try: - end = self._self_acquired_at = compat.monotonic_ns() + end = self._self_acquired_at = time.monotonic_ns() thread_id, thread_name = _current_thread() task_id, task_name, task_frame = _task.get_task(thread_id) self._maybe_update_self_name() @@ -185,7 +185,7 @@ def _release(self, inner_func, *args, **kwargs): try: if hasattr(self, "_self_acquired_at"): try: - end = compat.monotonic_ns() + end = time.monotonic_ns() thread_id, thread_name = _current_thread() task_id, task_name, task_frame = _task.get_task(thread_id) lock_name = ( diff --git a/ddtrace/profiling/collector/memalloc.py b/ddtrace/profiling/collector/memalloc.py index 62d4b059214..549132ed244 100644 --- a/ddtrace/profiling/collector/memalloc.py +++ b/ddtrace/profiling/collector/memalloc.py @@ -3,6 +3,7 @@ from math import ceil import os import threading +import time import typing # noqa:F401 from typing import Optional @@ -12,7 +13,6 @@ except ImportError: _memalloc = None # type: ignore[assignment] -from ddtrace.internal import compat from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling import _threading from ddtrace.profiling import collector @@ -189,7 +189,7 @@ def collect(self): if thread_id in thread_id_ignore_set: continue handle = ddup.SampleHandle() - handle.push_monotonic_ns(compat.monotonic_ns()) + handle.push_monotonic_ns(time.monotonic_ns()) handle.push_alloc(int((ceil(size) * alloc_count) / count), count) # Roundup to help float precision handle.push_threadinfo( thread_id, _threading.get_thread_native_id(thread_id), _threading.get_thread_name(thread_id) diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index c7ba1ec3e83..46b24e39c33 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -4,13 +4,13 @@ from __future__ import absolute_import from itertools import chain import logging import sys +import time import typing from ddtrace.internal._unpatched import _threading as ddtrace_threading from ddtrace._trace import context from ddtrace._trace import span as ddspan from ddtrace._trace.tracer import Tracer -from ddtrace.internal import compat from ddtrace.internal._threads import periodic_threads from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.datadog.profiling import stack_v2 @@ -131,10 +131,10 @@ ELSE: cdef stdint.int64_t _last_process_time def __init__(self): - self._last_process_time = compat.process_time_ns() + self._last_process_time = time.process_time_ns() def __call__(self, pthread_ids): - current_process_time = compat.process_time_ns() + current_process_time = time.process_time_ns() cpu_time = current_process_time - self._last_process_time self._last_process_time = current_process_time # Spread the consumed CPU time on all threads. @@ -524,7 +524,7 @@ class StackCollector(collector.PeriodicCollector): def _init(self): # type: (...) -> None self._thread_time = _ThreadTime() - self._last_wall_time = compat.monotonic_ns() + self._last_wall_time = time.monotonic_ns() if self.tracer is not None: self._thread_span_links = _ThreadSpanLinks() link_span = stack_v2.link_span if self._stack_collector_v2_enabled else self._thread_span_links.link_span @@ -569,7 +569,7 @@ class StackCollector(collector.PeriodicCollector): def collect(self): # Compute wall time - now = compat.monotonic_ns() + now = time.monotonic_ns() wall_time = now - self._last_wall_time self._last_wall_time = now all_events = [] @@ -587,7 +587,7 @@ class StackCollector(collector.PeriodicCollector): now_ns=now, ) - used_wall_time_ns = compat.monotonic_ns() - now + used_wall_time_ns = time.monotonic_ns() - now self.interval = self._compute_new_interval(used_wall_time_ns) if self._stack_collector_v2_enabled: diff --git a/ddtrace/profiling/scheduler.py b/ddtrace/profiling/scheduler.py index e8aafe7a63b..98ab424c42b 100644 --- a/ddtrace/profiling/scheduler.py +++ b/ddtrace/profiling/scheduler.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- import logging +import time from typing import Any # noqa F401 from typing import Callable from typing import Dict # noqa F401 @@ -9,7 +10,6 @@ import ddtrace from ddtrace._trace.tracer import Tracer -from ddtrace.internal import compat from ddtrace.internal import periodic from ddtrace.internal.datadog.profiling import ddup from ddtrace.profiling import _traceback @@ -49,7 +49,7 @@ def _start_service(self): """Start the scheduler.""" LOG.debug("Starting scheduler") super(Scheduler, self)._start_service() - self._last_export = compat.time_ns() + self._last_export = time.time_ns() LOG.debug("Scheduler started") def flush(self): @@ -68,14 +68,14 @@ def flush(self): # These are only used by the Python uploader, but set them here to keep logs/etc # consistent for now start = self._last_export - self._last_export = compat.time_ns() + self._last_export = time.time_ns() return events: EventsType = {} if self.recorder: events = self.recorder.reset() start = self._last_export - self._last_export = compat.time_ns() + self._last_export = time.time_ns() if self.exporters: for exp in self.exporters: try: @@ -90,11 +90,11 @@ def flush(self): def periodic(self): # type: (...) -> None - start_time = compat.monotonic() + start_time = time.monotonic() try: self.flush() finally: - self.interval = max(0, self._configured_interval - (compat.monotonic() - start_time)) + self.interval = max(0, self._configured_interval - (time.monotonic() - start_time)) class ServerlessScheduler(Scheduler): @@ -119,7 +119,7 @@ def __init__(self, *args, **kwargs): def periodic(self): # type: (...) -> None # Check both the number of intervals and time frame to be sure we don't flush, e.g., empty profiles - if self._profiled_intervals >= self.FLUSH_AFTER_INTERVALS and (compat.time_ns() - self._last_export) >= ( + if self._profiled_intervals >= self.FLUSH_AFTER_INTERVALS and (time.time_ns() - self._last_export) >= ( self.FORCED_INTERVAL * self.FLUSH_AFTER_INTERVALS ): try: diff --git a/ddtrace/vendor/__init__.py b/ddtrace/vendor/__init__.py index 1b9596e82da..c74ff435562 100644 --- a/ddtrace/vendor/__init__.py +++ b/ddtrace/vendor/__init__.py @@ -26,19 +26,6 @@ removed unnecessary compat utils -monotonic ---------- - -Website: https://pypi.org/project/monotonic/ -Source: https://github.com/atdt/monotonic -Version: 1.5 -License: Apache License 2.0 - -Notes: - The source `monotonic.py` was added as `monotonic/__init__.py` - - No other changes were made - debtcollector ------------- @@ -107,7 +94,7 @@ yacc.py and lex.py files here. Didn't copy: cpp.py, ctokens.py, ygen.py (didn't see them used) - + jsonpath-ng --------- diff --git a/ddtrace/vendor/monotonic/__init__.py b/ddtrace/vendor/monotonic/__init__.py deleted file mode 100644 index 4ad147bae80..00000000000 --- a/ddtrace/vendor/monotonic/__init__.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -""" - monotonic - ~~~~~~~~~ - - This module provides a ``monotonic()`` function which returns the - value (in fractional seconds) of a clock which never goes backwards. - - On Python 3.3 or newer, ``monotonic`` will be an alias of - ``time.monotonic`` from the standard library. On older versions, - it will fall back to an equivalent implementation: - - +-------------+----------------------------------------+ - | Linux, BSD | ``clock_gettime(3)`` | - +-------------+----------------------------------------+ - | Windows | ``GetTickCount`` or ``GetTickCount64`` | - +-------------+----------------------------------------+ - | OS X | ``mach_absolute_time`` | - +-------------+----------------------------------------+ - - If no suitable implementation exists for the current platform, - attempting to import this module (or to import from it) will - cause a ``RuntimeError`` exception to be raised. - - - Copyright 2014, 2015, 2016 Ori Livneh - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -""" -import time - - -__all__ = ('monotonic',) - - -try: - monotonic = time.monotonic -except AttributeError: - import ctypes - import ctypes.util - import os - import sys - import threading - try: - if sys.platform == 'darwin': # OS X, iOS - # See Technical Q&A QA1398 of the Mac Developer Library: - # - libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True) - - class mach_timebase_info_data_t(ctypes.Structure): - """System timebase info. Defined in .""" - _fields_ = (('numer', ctypes.c_uint32), - ('denom', ctypes.c_uint32)) - - mach_absolute_time = libc.mach_absolute_time - mach_absolute_time.restype = ctypes.c_uint64 - - timebase = mach_timebase_info_data_t() - libc.mach_timebase_info(ctypes.byref(timebase)) - ticks_per_second = timebase.numer / timebase.denom * 1.0e9 - - def monotonic(): - """Monotonic clock, cannot go backward.""" - return mach_absolute_time() / ticks_per_second - - elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'): - if sys.platform.startswith('cygwin'): - # Note: cygwin implements clock_gettime (CLOCK_MONOTONIC = 4) since - # version 1.7.6. Using raw WinAPI for maximum version compatibility. - - # Ugly hack using the wrong calling convention (in 32-bit mode) - # because ctypes has no windll under cygwin (and it also seems that - # the code letting you select stdcall in _ctypes doesn't exist under - # the preprocessor definitions relevant to cygwin). - # This is 'safe' because: - # 1. The ABI of GetTickCount and GetTickCount64 is identical for - # both calling conventions because they both have no parameters. - # 2. libffi masks the problem because after making the call it doesn't - # touch anything through esp and epilogue code restores a correct - # esp from ebp afterwards. - try: - kernel32 = ctypes.cdll.kernel32 - except OSError: # 'No such file or directory' - kernel32 = ctypes.cdll.LoadLibrary('kernel32.dll') - else: - kernel32 = ctypes.windll.kernel32 - - GetTickCount64 = getattr(kernel32, 'GetTickCount64', None) - if GetTickCount64: - # Windows Vista / Windows Server 2008 or newer. - GetTickCount64.restype = ctypes.c_ulonglong - - def monotonic(): - """Monotonic clock, cannot go backward.""" - return GetTickCount64() / 1000.0 - - else: - # Before Windows Vista. - GetTickCount = kernel32.GetTickCount - GetTickCount.restype = ctypes.c_uint32 - - get_tick_count_lock = threading.Lock() - get_tick_count_last_sample = 0 - get_tick_count_wraparounds = 0 - - def monotonic(): - """Monotonic clock, cannot go backward.""" - global get_tick_count_last_sample - global get_tick_count_wraparounds - - with get_tick_count_lock: - current_sample = GetTickCount() - if current_sample < get_tick_count_last_sample: - get_tick_count_wraparounds += 1 - get_tick_count_last_sample = current_sample - - final_milliseconds = get_tick_count_wraparounds << 32 - final_milliseconds += get_tick_count_last_sample - return final_milliseconds / 1000.0 - - else: - try: - clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'), - use_errno=True).clock_gettime - except Exception: - clock_gettime = ctypes.CDLL(ctypes.util.find_library('rt'), - use_errno=True).clock_gettime - - class timespec(ctypes.Structure): - """Time specification, as described in clock_gettime(3).""" - _fields_ = (('tv_sec', ctypes.c_long), - ('tv_nsec', ctypes.c_long)) - - if sys.platform.startswith('linux'): - CLOCK_MONOTONIC = 1 - elif sys.platform.startswith('freebsd'): - CLOCK_MONOTONIC = 4 - elif sys.platform.startswith('sunos5'): - CLOCK_MONOTONIC = 4 - elif 'bsd' in sys.platform: - CLOCK_MONOTONIC = 3 - elif sys.platform.startswith('aix'): - CLOCK_MONOTONIC = ctypes.c_longlong(10) - - def monotonic(): - """Monotonic clock, cannot go backward.""" - ts = timespec() - if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(ts)): - errno = ctypes.get_errno() - raise OSError(errno, os.strerror(errno)) - return ts.tv_sec + ts.tv_nsec / 1.0e9 - - # Perform a sanity-check. - if monotonic() - monotonic() > 0: - raise ValueError('monotonic() is not monotonic!') - - except Exception as e: - raise RuntimeError('no suitable implementation for this system: ' + repr(e)) diff --git a/tests/debugging/mocking.py b/tests/debugging/mocking.py index dee76125ba3..4446bce559c 100644 --- a/tests/debugging/mocking.py +++ b/tests/debugging/mocking.py @@ -2,6 +2,7 @@ from collections import Counter from contextlib import contextmanager import json +from time import monotonic from time import sleep from typing import Any from typing import Generator @@ -16,7 +17,6 @@ from ddtrace.debugging._probe.remoteconfig import _filter_by_env_and_version from ddtrace.debugging._signal.collector import SignalCollector from ddtrace.debugging._uploader import LogsIntakeUploaderV1 -from ddtrace.internal.compat import monotonic from tests.debugging.probe.test_status import DummyProbeStatusLogger diff --git a/tests/integration/test_sampling.py b/tests/integration/test_sampling.py index bb0d421a2d1..902b430bbc8 100644 --- a/tests/integration/test_sampling.py +++ b/tests/integration/test_sampling.py @@ -331,7 +331,7 @@ def test_rate_limiter_on_long_running_spans(tracer): """ tracer.configure(sampler=DatadogSampler(rate_limit=5)) - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=1617333414): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=1617333414): span_m30 = tracer.trace(name="march 30") span_m30.start = 1622347257 # Mar 30 2021 span_m30.finish(1617333414) # April 2 2021 diff --git a/tests/profiling/exporter/test_http.py b/tests/profiling/exporter/test_http.py index 42e3aeb6fe6..26b69ff495e 100644 --- a/tests/profiling/exporter/test_http.py +++ b/tests/profiling/exporter/test_http.py @@ -201,14 +201,15 @@ def test_wrong_api_key(endpoint_test_server): @pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL=_ENDPOINT)) def test_export(endpoint_test_server): - from ddtrace.internal import compat + import time + from ddtrace.profiling.exporter import http from tests.profiling.exporter import test_pprof from tests.profiling.exporter.test_http import _API_KEY from tests.profiling.exporter.test_http import _get_span_processor exp = http.PprofHTTPExporter(api_key=_API_KEY, endpoint_call_counter_span_processor=_get_span_processor()) - exp.export(test_pprof.TEST_EVENTS, 0, compat.time_ns()) + exp.export(test_pprof.TEST_EVENTS, 0, time.time_ns()) @pytest.mark.subprocess(env=dict(DD_TRACE_AGENT_URL="http://localhost:2")) diff --git a/tests/profiling/test_accuracy.py b/tests/profiling/test_accuracy.py index 7d94e725b72..d5fcc030ef9 100644 --- a/tests/profiling/test_accuracy.py +++ b/tests/profiling/test_accuracy.py @@ -3,8 +3,6 @@ import pytest -from ddtrace.internal import compat - def spend_1(): time.sleep(1) @@ -33,16 +31,16 @@ def spend_16(): def spend_cpu_2(): - now = compat.monotonic_ns() + now = time.monotonic_ns() # Active wait for 2 seconds - while compat.monotonic_ns() - now < 2e9: + while time.monotonic_ns() - now < 2e9: pass def spend_cpu_3(): # Active wait for 3 seconds - now = compat.monotonic_ns() - while compat.monotonic_ns() - now < 3e9: + now = time.monotonic_ns() + while time.monotonic_ns() - now < 3e9: pass diff --git a/tests/profiling/test_scheduler.py b/tests/profiling/test_scheduler.py index 483ec643527..42d8ccc29fe 100644 --- a/tests/profiling/test_scheduler.py +++ b/tests/profiling/test_scheduler.py @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- import logging +import time import mock -from ddtrace.internal import compat from ddtrace.profiling import event from ddtrace.profiling import exporter from ddtrace.profiling import recorder @@ -64,11 +64,11 @@ def test_serverless_periodic(mock_periodic): r = recorder.Recorder() s = scheduler.ServerlessScheduler(r, [exporter.NullExporter()]) # Fake start() - s._last_export = compat.time_ns() + s._last_export = time.time_ns() s.periodic() assert s._profiled_intervals == 1 mock_periodic.assert_not_called() - s._last_export = compat.time_ns() - 65 + s._last_export = time.time_ns() - 65 s._profiled_intervals = 65 s.periodic() assert s._profiled_intervals == 0 diff --git a/tests/submod/stuff.py b/tests/submod/stuff.py index d2fa07e0ec5..375520bf76b 100644 --- a/tests/submod/stuff.py +++ b/tests/submod/stuff.py @@ -125,7 +125,7 @@ def __init__(self): foo = property(operator.attrgetter("_foo")) -from ddtrace.internal.compat import monotonic_ns # noqa:E402 +from time import monotonic_ns # noqa:E402 def durationstuff(ns): diff --git a/tests/tracer/test_rate_limiter.py b/tests/tracer/test_rate_limiter.py index d66f980cbc3..df9e1458525 100644 --- a/tests/tracer/test_rate_limiter.py +++ b/tests/tracer/test_rate_limiter.py @@ -1,9 +1,10 @@ from __future__ import division +import time + import mock import pytest -from ddtrace.internal import compat from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded @@ -20,7 +21,7 @@ def test_rate_limiter_init(time_window): assert limiter.rate_limit == 100 assert limiter.tokens == 100 assert limiter.max_tokens == 100 - assert limiter.last_update_ns <= compat.monotonic_ns() + assert limiter.last_update_ns <= time.monotonic_ns() @pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) @@ -30,10 +31,10 @@ def test_rate_limiter_rate_limit_0(time_window): assert limiter.tokens == 0 assert limiter.max_tokens == 0 - now_ns = compat.monotonic_ns() + now_ns = time.monotonic_ns() for i in nanoseconds(10000, time_window): # Make sure the time is different for every check - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now_ns + i): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now_ns + i): assert limiter.is_allowed() is False @@ -44,10 +45,10 @@ def test_rate_limiter_rate_limit_negative(time_window): assert limiter.tokens == -1 assert limiter.max_tokens == -1 - now_ns = compat.monotonic_ns() + now_ns = time.monotonic_ns() for i in nanoseconds(10000, time_window): # Make sure the time is different for every check - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now_ns + i): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now_ns + i): assert limiter.is_allowed() is True @@ -66,12 +67,12 @@ def check_limit(): assert limiter.is_allowed() is False # Start time - now = compat.monotonic_ns() + now = time.monotonic_ns() # Check the limit for 5 time frames for i in nanoseconds(5, time_window): # Keep the same timeframe - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now + i): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now + i): check_limit() @@ -80,14 +81,14 @@ def test_rate_limiter_is_allowed_large_gap(time_window): limiter = RateLimiter(rate_limit=100, time_window=time_window) # Start time - now_ns = compat.monotonic_ns() + now_ns = time.monotonic_ns() # Keep the same timeframe - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now_ns): for _ in range(100): assert limiter.is_allowed() is True # Large gap before next call to `is_allowed()` - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now_ns + (time_window * 100)): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now_ns + (time_window * 100)): for _ in range(100): assert limiter.is_allowed() is True @@ -97,13 +98,13 @@ def test_rate_limiter_is_allowed_small_gaps(time_window): limiter = RateLimiter(rate_limit=100, time_window=time_window) # Start time - now_ns = compat.monotonic_ns() + now_ns = time.monotonic_ns() gap = 1e9 / 100 # Keep incrementing by a gap to keep us at our rate limit for i in nanoseconds(10000, time_window): # Keep the same timeframe time_ns = now_ns + (gap * i) - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=time_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=time_ns): assert limiter.is_allowed() is True @@ -112,8 +113,8 @@ def test_rate_liimter_effective_rate_rates(time_window): limiter = RateLimiter(rate_limit=100, time_window=time_window) # Static rate limit window - starting_window_ns = compat.monotonic_ns() - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=starting_window_ns): + starting_window_ns = time.monotonic_ns() + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=starting_window_ns): for _ in range(100): assert limiter.is_allowed() is True assert limiter.effective_rate == 1.0 @@ -127,7 +128,7 @@ def test_rate_liimter_effective_rate_rates(time_window): prev_rate = 0.5 window_ns = starting_window_ns + time_window - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=window_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=window_ns): for _ in range(100): assert limiter.is_allowed() is True assert limiter.effective_rate == 0.75 @@ -144,7 +145,7 @@ def test_rate_liimter_effective_rate_rates(time_window): def test_rate_limiter_effective_rate_starting_rate(time_window): limiter = RateLimiter(rate_limit=1, time_window=time_window) - now_ns = compat.monotonic_ns() + now_ns = time.monotonic_ns() # Default values assert limiter.current_window_ns == 0 @@ -156,7 +157,7 @@ def test_rate_limiter_effective_rate_starting_rate(time_window): assert limiter.prev_window_rate is None # Calling `.is_allowed()` updates the values - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=now_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=now_ns): assert limiter.is_allowed() is True assert limiter.effective_rate == 1.0 assert limiter.current_window_ns == now_ns @@ -164,7 +165,7 @@ def test_rate_limiter_effective_rate_starting_rate(time_window): # Gap of 0.85 seconds, same window time_ns = now_ns + (0.85 * time_window) - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=time_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=time_ns): assert limiter.is_allowed() is False # DEV: We have rate_limit=1 set assert limiter.effective_rate == 0.5 @@ -173,7 +174,7 @@ def test_rate_limiter_effective_rate_starting_rate(time_window): # Gap of 1.0 seconds, new window time_ns = now_ns + time_window - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=time_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=time_ns): assert limiter.is_allowed() is True assert limiter.effective_rate == 0.75 assert limiter.current_window_ns == (now_ns + time_window) @@ -181,7 +182,7 @@ def test_rate_limiter_effective_rate_starting_rate(time_window): # Gap of 1.85 seconds, same window time_ns = now_ns + (1.85 * time_window) - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=time_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=time_ns): assert limiter.is_allowed() is False assert limiter.effective_rate == 0.5 assert limiter.current_window_ns == (now_ns + time_window) # Same as old window @@ -189,7 +190,7 @@ def test_rate_limiter_effective_rate_starting_rate(time_window): # Large gap of 100 seconds, new window time_ns = now_ns + (100.0 * time_window) - with mock.patch("ddtrace.internal.rate_limiter.compat.monotonic_ns", return_value=time_ns): + with mock.patch("ddtrace.internal.rate_limiter.time.monotonic_ns", return_value=time_ns): assert limiter.is_allowed() is True assert limiter.effective_rate == 0.75 assert limiter.current_window_ns == (now_ns + (100.0 * time_window)) From 3d08709dd0faceb9ca33cb1432c9fe06f989371d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:43:04 +0000 Subject: [PATCH 355/372] chore: update supported versions (#11794) Generates / updates the supported versions table for integrations. This should be tied to releases, or triggered manually. Workflow runs: [Generate Supported Integration Versions](https://github.com/DataDog/dd-trace-py/actions/workflows/generate-supported-versions.yml) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> Co-authored-by: Quinna Halim --- supported_versions_output.json | 309 +++++++++++++++++++++++++++++++++ supported_versions_table.csv | 51 ++++++ 2 files changed, 360 insertions(+) create mode 100644 supported_versions_output.json create mode 100644 supported_versions_table.csv diff --git a/supported_versions_output.json b/supported_versions_output.json new file mode 100644 index 00000000000..a51bb17bb9a --- /dev/null +++ b/supported_versions_output.json @@ -0,0 +1,309 @@ +[ + { + "integration": "aiobotocore", + "minimum_tracer_supported": "1.4.2", + "max_tracer_supported": "2.13.3", + "auto-instrumented": false + }, + { + "integration": "aiohttp", + "minimum_tracer_supported": "3.8.6", + "max_tracer_supported": "3.11.10", + "auto-instrumented": true + }, + { + "integration": "aiomysql", + "minimum_tracer_supported": "0.1.1", + "max_tracer_supported": "0.2.0", + "auto-instrumented": true + }, + { + "integration": "aiopg", + "minimum_tracer_supported": "1.4.0", + "max_tracer_supported": "1.4.0", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "algoliasearch", + "minimum_tracer_supported": "2.6.3", + "max_tracer_supported": "2.6.3", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "anthropic", + "minimum_tracer_supported": "0.26.0", + "max_tracer_supported": "0.40.0", + "auto-instrumented": true + }, + { + "integration": "aredis", + "minimum_tracer_supported": "1.1.8", + "max_tracer_supported": "1.1.8", + "auto-instrumented": true + }, + { + "integration": "asyncpg", + "minimum_tracer_supported": "0.23.0", + "max_tracer_supported": "0.30.0", + "auto-instrumented": true + }, + { + "integration": "avro", + "minimum_tracer_supported": "1.12.0", + "max_tracer_supported": "1.12.0", + "auto-instrumented": true + }, + { + "integration": "boto", + "minimum_tracer_supported": "2.49.0", + "max_tracer_supported": "2.49.0", + "auto-instrumented": true + }, + { + "integration": "botocore", + "minimum_tracer_supported": "1.20.106", + "max_tracer_supported": "1.35.78", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "bottle", + "minimum_tracer_supported": "0.12.25", + "max_tracer_supported": "0.13.2", + "auto-instrumented": true + }, + { + "integration": "celery", + "minimum_tracer_supported": "4.4.7", + "max_tracer_supported": "5.4.0", + "auto-instrumented": true + }, + { + "integration": "cherrypy", + "minimum_tracer_supported": "17.4.2", + "max_tracer_supported": "18.10.0", + "auto-instrumented": false + }, + { + "integration": "coverage", + "minimum_tracer_supported": "7.2.7", + "max_tracer_supported": "7.4.4", + "auto-instrumented": false + }, + { + "integration": "django", + "minimum_tracer_supported": "2.2.1", + "max_tracer_supported": "5.1", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "dramatiq", + "minimum_tracer_supported": "1.16.0", + "max_tracer_supported": "1.17.0", + "auto-instrumented": true + }, + { + "integration": "falcon", + "minimum_tracer_supported": "3.0.1", + "max_tracer_supported": "3.1.3", + "auto-instrumented": true + }, + { + "integration": "fastapi", + "minimum_tracer_supported": "0.64.0", + "max_tracer_supported": "0.115.6", + "auto-instrumented": true + }, + { + "integration": "flask", + "minimum_tracer_supported": "0.12.5", + "max_tracer_supported": "3.0.3", + "auto-instrumented": true + }, + { + "integration": "gevent", + "minimum_tracer_supported": "20.12.1", + "max_tracer_supported": "24.11.1", + "auto-instrumented": true + }, + { + "integration": "httpx", + "minimum_tracer_supported": "0.15.4", + "max_tracer_supported": "0.28.1", + "auto-instrumented": true + }, + { + "integration": "jinja2", + "minimum_tracer_supported": "2.10.3", + "max_tracer_supported": "3.1.4", + "auto-instrumented": true + }, + { + "integration": "kombu", + "minimum_tracer_supported": "4.2.2.post1", + "max_tracer_supported": "5.4.2", + "auto-instrumented": false + }, + { + "integration": "langchain", + "minimum_tracer_supported": "0.0.192", + "max_tracer_supported": "0.3.10", + "auto-instrumented": true + }, + { + "integration": "logbook", + "minimum_tracer_supported": "1.0.0", + "max_tracer_supported": "1.7.0.post0", + "auto-instrumented": false + }, + { + "integration": "loguru", + "minimum_tracer_supported": "0.4.1", + "max_tracer_supported": "0.7.3", + "auto-instrumented": false + }, + { + "integration": "mako", + "minimum_tracer_supported": "1.1.6", + "max_tracer_supported": "1.3.5", + "auto-instrumented": true + }, + { + "integration": "mariadb", + "minimum_tracer_supported": "1.0.11", + "max_tracer_supported": "1.1.11", + "auto-instrumented": true + }, + { + "integration": "molten", + "minimum_tracer_supported": "1.0.2", + "max_tracer_supported": "1.0.2", + "auto-instrumented": true + }, + { + "integration": "mongoengine", + "minimum_tracer_supported": "0.29.1", + "max_tracer_supported": "0.29.1", + "auto-instrumented": true + }, + { + "integration": "openai", + "minimum_tracer_supported": "0.26.5", + "max_tracer_supported": "1.57.2", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "protobuf", + "minimum_tracer_supported": "3.8.0", + "max_tracer_supported": "5.28.3", + "auto-instrumented": false + }, + { + "integration": "pylibmc", + "minimum_tracer_supported": "1.6.3", + "max_tracer_supported": "1.6.3", + "auto-instrumented": true + }, + { + "integration": "pymemcache", + "minimum_tracer_supported": "3.4.4", + "max_tracer_supported": "4.0.0", + "auto-instrumented": true + }, + { + "integration": "pymongo", + "minimum_tracer_supported": "3.8.0", + "max_tracer_supported": "4.10.1", + "auto-instrumented": true + }, + { + "integration": "pymysql", + "minimum_tracer_supported": "0.10.1", + "max_tracer_supported": "1.1.1", + "auto-instrumented": true + }, + { + "integration": "pynamodb", + "minimum_tracer_supported": "5.5.1", + "max_tracer_supported": "5.5.1", + "pinned": "true", + "auto-instrumented": true + }, + { + "integration": "pyodbc", + "minimum_tracer_supported": "4.0.39", + "max_tracer_supported": "5.2.0", + "auto-instrumented": true + }, + { + "integration": "pyramid", + "minimum_tracer_supported": "1.10.8", + "max_tracer_supported": "2.0.2", + "auto-instrumented": true + }, + { + "integration": "redis", + "minimum_tracer_supported": "2.10.6", + "max_tracer_supported": "5.2.1", + "auto-instrumented": true + }, + { + "integration": "requests", + "minimum_tracer_supported": "2.20.1", + "max_tracer_supported": "2.32.3", + "auto-instrumented": true + }, + { + "integration": "sanic", + "minimum_tracer_supported": "20.12.7", + "max_tracer_supported": "24.6.0", + "auto-instrumented": true + }, + { + "integration": "sqlalchemy", + "minimum_tracer_supported": "1.2.19", + "max_tracer_supported": "2.0.36", + "auto-instrumented": false + }, + { + "integration": "starlette", + "minimum_tracer_supported": "0.13.6", + "max_tracer_supported": "0.41.3", + "auto-instrumented": true + }, + { + "integration": "structlog", + "minimum_tracer_supported": "20.2.0", + "max_tracer_supported": "24.4.0", + "auto-instrumented": false + }, + { + "integration": "tornado", + "minimum_tracer_supported": "4.5.3", + "max_tracer_supported": "6.4", + "pinned": "true", + "auto-instrumented": false + }, + { + "integration": "urllib3", + "minimum_tracer_supported": "1.24.3", + "max_tracer_supported": "2.2.3", + "auto-instrumented": false + }, + { + "integration": "vertexai", + "minimum_tracer_supported": "1.71.1", + "max_tracer_supported": "1.71.1", + "auto-instrumented": true + }, + { + "integration": "yaaredis", + "minimum_tracer_supported": "2.0.4", + "max_tracer_supported": "3.0.0", + "auto-instrumented": true + } +] \ No newline at end of file diff --git a/supported_versions_table.csv b/supported_versions_table.csv new file mode 100644 index 00000000000..3f7384a0cdd --- /dev/null +++ b/supported_versions_table.csv @@ -0,0 +1,51 @@ +integration,minimum_tracer_supported,max_tracer_supported,auto-instrumented +aiobotocore,1.4.2,2.13.3,False +aiohttp,3.8.6,3.11.10,True +aiomysql,0.1.1,0.2.0,True +aiopg *,1.4.0,1.4.0,True +algoliasearch *,2.6.3,2.6.3,True +anthropic,0.26.0,0.40.0,True +aredis,1.1.8,1.1.8,True +asyncpg,0.23.0,0.30.0,True +avro,1.12.0,1.12.0,True +boto,2.49.0,2.49.0,True +botocore *,1.20.106,1.35.78,True +bottle,0.12.25,0.13.2,True +celery,4.4.7,5.4.0,True +cherrypy,17.4.2,18.10.0,False +coverage,7.2.7,7.4.4,False +django *,2.2.1,5.1,True +dramatiq,1.16.0,1.17.0,True +falcon,3.0.1,3.1.3,True +fastapi,0.64.0,0.115.6,True +flask,0.12.5,3.0.3,True +gevent,20.12.1,24.11.1,True +httpx,0.15.4,0.28.1,True +jinja2,2.10.3,3.1.4,True +kombu,4.2.2.post1,5.4.2,False +langchain,0.0.192,0.3.10,True +logbook,1.0.0,1.7.0.post0,False +loguru,0.4.1,0.7.3,False +mako,1.1.6,1.3.5,True +mariadb,1.0.11,1.1.11,True +molten,1.0.2,1.0.2,True +mongoengine,0.29.1,0.29.1,True +openai *,0.26.5,1.57.2,True +protobuf,3.8.0,5.28.3,False +pylibmc,1.6.3,1.6.3,True +pymemcache,3.4.4,4.0.0,True +pymongo,3.8.0,4.10.1,True +pymysql,0.10.1,1.1.1,True +pynamodb *,5.5.1,5.5.1,True +pyodbc,4.0.39,5.2.0,True +pyramid,1.10.8,2.0.2,True +redis,2.10.6,5.2.1,True +requests,2.20.1,2.32.3,True +sanic,20.12.7,24.6.0,True +sqlalchemy,1.2.19,2.0.36,False +starlette,0.13.6,0.41.3,True +structlog,20.2.0,24.4.0,False +tornado *,4.5.3,6.4,False +urllib3,1.24.3,2.2.3,False +vertexai,1.71.1,1.71.1,True +yaaredis,2.0.4,3.0.0,True From 6f3bf255cf1d729b4447c732734d92721a95b1fe Mon Sep 17 00:00:00 2001 From: wantsui Date: Fri, 20 Dec 2024 14:01:54 -0500 Subject: [PATCH 356/372] chore: update changelog for version 2.18.1, 2.18.0, 2.17.3, 2.16.6 (#11815) - [x] update changelog for version 2.18.1, 2.18.0, 2.17.3, 2.16.6 --------- Co-authored-by: erikayasuda Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- CHANGELOG.md | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d259f8624a..2039d597f9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,164 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.18.1 + + +### Bug Fixes + +Profiling: +- Fixes an issue where the memory allocation profiler can cause a segmentation fault due to data races when accessing its own global data structures from multiple threads. +- Fixes a bug where profiling mutexes were not cleared on fork in the child process. This could cause deadlocks in certain configurations. + +Tracing: +- celery: Fixes an issue where `celery.apply` spans from Celery prerun got closed too soon leading to span tags being missing. + +--- + +## 2.18.0 + +### Upgrade Notes +- ASM + - With this upgrade, you can now control how the stack trace report are cropped when reported for exploit prevention or IAST. + - `DD_APPSEC_MAX_STACK_TRACE_DEPTH` allowed to control the maximum stack trace size reported (default 32) + - `DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT` allows now to specify how the stack trace is cropped as a percentage. + + For example, a value of 100 will report the top DD_APPSEC_MAX_STACK_TRACE_DEPTH frames from the stack, while a value of 0 will report the bottom DD_APPSEC_MAX_STACK_TRACE_DEPTH frames of the trace. A value of 50 will report half of DD_APPSEC_MAX_STACK_TRACE_DEPTH (rounded down) frames from the top of the stack and the rest from bottom. Default value is 75. + - Upgrades `libddwaf` to 1.22.0 + - Upgrades `libddwaf` to 1.21.0 and security rule file to 1.13.3 + + +### Deprecation Notes +- Python 3.7 support is deprecated and will be removed in 3.0 + + +### New Features +- CI Visibility + - Beta release of the new version of the pytest plugin, introducing the following features: + - [Auto Test Retries](https://docs.datadoghq.com/tests/flaky_test_management/auto_test_retries) + - [Early Flake Detection](https://docs.datadoghq.com/tests/flaky_test_management/early_flake_detection) + - Improved coverage collection for [Test Impact Analysis](https://docs.datadoghq.com/tests/test_impact_analysis) (formerly Intelligent Test Runner) now uses an internal collection method instead of [coverage.py](https://github.com/nedbat/coveragepy), with improved dependency discovery. + + Set the `DD_PYTEST_USE_NEW_PLUGIN_BETA` environment variable to `true` to use this new version. + + **NOTE:** this new version of the plugin introduces breaking changes: + - `module`, `suite`, and `test` names are now parsed from the `item.nodeid` attribute + - test names now include the class for class-based tests + - Test skipping by Test Impact Analysis (formerly Intelligent Test Runner) is now done at the suite level, instead of at the test level +- Adds support for [Selenium and RUM integration](https://docs.datadoghq.com/tests/browser_tests/) + +- Code Security + - Introduces "Standalone Code Security", a feature that disables APM in the tracer but keeps Code Security (IAST) enabled. In order to enable it, set the environment variables `DD_IAST_ENABLED=1` and `DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED=1`. + +- LLM Observability + - Adds support to automatically submit Vertex AI Python calls to LLM Observability. + - `vertexai`: Introduces tracing support for Google's Vertex AI SDK for Python's `generate_content` and `send_message` calls. See [the docs](https://ddtrace.readthedocs.io/en/stable/integrations.html#vertexai) for more information. + +- Profiling + - Profiler uses agent url configured via `tracer.configure()` + + +### Bug Fixes +- ASM + - Ensures that common patches for exploit prevention and sca are only loaded if required, and only loaded once. + - Resolves an issue where AppSec was using a patched JSON loads, creating telemetry errors. + - Resolves an issue where some root span where not appropriately tagged for ASM standalone. + - ASM: Resolves an issue where AppSec was using a patched request and builtins functions, + creating telemetry errors. + +- CI Visibility + - Fixes an issue where the CIVisbility service would incorrectly default the tracer env to `None` in EVP proxy mode if `DD_ENV` was not specified but the agent had a default environment set to a value other than `none` (eg: using `DD_APM_ENV` in the agent's environment). + - Updates the inferred base service name algorithm to ensure that arguments following `--ddtrace` are no longer skipped when executing tests with pytest. Previously, the algorithm misinterpreted these arguments as standard flags, overlooking possible test paths that may contribute to the inferred service name. + +- Code Security + - Patches the module dir function so original pre-patch results are not changed. + - Resolves a patching issue with `psycopg3`. + - This fix resolves an issue where the modulo (%) operator would not be replaced correctly for bytes and bytesarray if IAST is enabled. + - Ensures IAST SSRF vulnerability redacts the url query parameters correctly. + - Adds `umap`, `numba` and `pynndescent` to the Code Security denylist. + +- Crashtracking + - Resolves issue where the crashtracker receiver may leave a zombie process behind after a crash. + +- Lib-Injection + - Ensures any user defined `sitecustomize.py` are preserved when auto-injecting. + - Supports Python 2.7+ for injection compatibility check. + - Resolves an issue where the default versions of `click` and `jinja2` installed on 3.8 were outside of the allowed minimum versions for autoinstrumentation. + +- LLM Observability + - Ensures bedrock spans are finished even when streamed responses are not fully consumed. + - `langchain`: Resolves a JSON decoding issue resulting from tagging streamed outputs from chains ending with a PydanticOutputParser. + - Fixes an issue where decorators were not tracing generator functions properly. + +- Profiling + - Updates setup.py to ignore int-ptr conversion warnings for the profiler stack.pyx file. This is important because gcc 14 makes these conversions an error, alpine 3.21.0 ships with gcc 14, and any patch version of a Python alpine image cut after December 5th, 2024, will have this issue. + - Fixes unbounded memory usage growth caused by keeping arbitrary user-generated strings (e.g. asyncio Task names) in an internal table and never removing them. + - Fixes an issue where `asyncio` task names are not properly propagated when using stack v2, i.e. when `DD_PROFILING_STACK_V2_ENABLED` is set. Fixes an issue where `asyncio` tasks are not associated with spans when using stack v2, i.e. when `DD_PROFILING_STACK_V2_ENABLED` is set. + +- Telemetry + - Ensures that Telemetry heartbeats are not skipped for forked processes, as doing so could result in the dependency list being lost over time. + +- Tracing + - `botocore`: This fix resolves an issue in the Bedrock integration where not consuming the full response stream would prevent spans from finishing. + - `botocore`: This fix resolves the issue where the span pointer for deserialized DynamoDB requests (through the resource-based API) were not being generated. + - `botocore`: This fix resolves an issue where our span pointer calculation code added recently logged unactionable messages. + - `celery`: This fix resolves two issues with context propagation in celery + 1. Invalid span parentage when task A calls task B async and task A errors out, causing A's queuing of B, and B itself to not be parented under A. + 2. Invalid context propagation from client to workers, and across retries, causing multiple traces instead of a single trace + - `celery`: Changes celery `out.host` span tag to point towards broker host url instead of local celery process hostname. Fixes inferred service representation issues when using celery. + - `grpcaio`: Resolves a concurrency bug where distributed tracing headers were overwritten resulting in spans being assigned to the wrong trace. + - `kafka`: Fixes an issue with Kafka consumer spans not using the active trace context when distributed tracing was enabled and no valid distributed context found was found within a consumed message. + + +### Other Changes +- Tracing + - Removed x-forwarded from headers used for client IP resolution (but not from collected headers). We lack evidence of actual usage, and whether this should follow RFC 7239 or regular XFF list format. + +--- + +## 2.17.3 + +### Bug Fixes + +- SCA: + - Ensure that Telemetry heartbeats are not skipped for forked processes, as doing so could result in the dependency list being lost over time. + +- Celery: + - This fix resolves two issues with context propagation in celery + - 1. Invalid span parentage when task A calls task B async and task A errors out, causing A's queuing of B, and B itself to not be parented under A. + - 2. Invalid context propagation from client to workers, and across retries, causing multiple traces instead of a single trace + +- Code Security: + - This fix resolves a patching issue with psycopg3. + - This fix resolves an issue where the modulo (%) operator would not be replaced correctly for bytes and bytesarray if IAST is enabled. + - Ensure IAST SSRF vulnerability redacts the url query parameters correctly. + +- Profiling: + - Updates setup.py to ignore int-ptr conversion warnings for the profiler stack.pyx file. This is important because gcc 14 makes these conversions an error, alpine 3.21.0 ships with gcc 14, and any patch version of a Python alpine image cut after December 5th, 2024, will have this issue. + +--- + +## 2.16.6 + +### Bug Fixes + +- SCA: + - Ensure that Telemetry heartbeats are not skipped for forked processes, as doing so could result in the dependency list being lost over time. + +- Code Security: + - Resolve a patching issue with psycopg3. + - Resolve an issue where the modulo (%) operator would not be replaced correctly for bytes and bytesarray if IAST is enabled. + - Ensure IAST SSRF vulnerability redacts the url query parameters correctly. + +- Lib-Injection: + - Fix injection guardrail check when sys.argv is not available. + +- Profiling + - Updates setup.py to ignore int-ptr conversion warnings for the profiler stack.pyx file. This is important because gcc 14 makes these conversions an error, alpine 3.21.0 ships with gcc 14, and any patch version of a Python alpine image cut after December 5th, 2024, will have this issue. + + --- ## 2.17.2 From 42f69dcf73545f6328a10056962bdfcd41020079 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:16:25 -0800 Subject: [PATCH 357/372] chore(docs): remove `DD_TRACE_ENABLED` from configuration docs (#11795) These are defined in the corporate docs, so we should just keep them there as the source of truth. The link to the corporate docs has also been added here. See generated docs for this branch [here](https://ddtrace.readthedocs.io/en/erikayasuda-remove-dd-trace-enabled/configuration.html) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/configuration.rst | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 556f72d40a3..d36391de07e 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -10,6 +10,12 @@ see specific integration documentation for more details. The following environment variables for the tracer are supported: +Common Configurations +--------------------- + +For common configuration variables (not language specific), see `Configure the Datadog Tracing Library`_. + + Unified Service Tagging ----------------------- @@ -177,18 +183,6 @@ Traces version_added: v2.17.0: - DD_TRACE_ENABLED: - type: Boolean - default: True - - description: | - Enable sending of spans to the Agent. Note that instrumentation will still be installed and spans will be - generated. - - version_added: - v0.41.0: | - Formerly named ``DATADOG_TRACE_ENABLED`` - DD_TRACE_HEADER_TAGS: description: | A map of case-insensitive http headers to tag names. Automatically applies matching header values as tags on request and response spans. For example if @@ -882,6 +876,8 @@ Other .. _Unified Service Tagging: https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/ +.. _Configure the Datadog Tracing Library: https://docs.datadoghq.com/tracing/trace_collection/library_config/ + Profiling --------- From 4beaa01a6ffe35681feba9f61dc277eed47b4d71 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 20 Dec 2024 14:51:34 -0500 Subject: [PATCH 358/372] chore(profiling): more tests for libdd and stack_v2 (#11679) First copied files from tests/profiling folder, and then modified them as needed. Tests checking for agent export urls are dropped. libdd exporter is created with agent url passed from tracer state whenever we try to upload, see [feat(profiling): support dynamic agent url](https://github.com/DataDog/dd-trace-py/pull/11319) These tests will be run under libdd_enabled and some of them had been edited to also run under stack_v2 enabled ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling/collector/test_stack.py | 37 ++-- tests/profiling/test_accuracy.py | 45 ++-- tests/profiling_v2/collector/test_stack.py | 131 ++++++++++- tests/profiling_v2/simple_program.py | 32 +++ tests/profiling_v2/simple_program_fork.py | 32 +++ tests/profiling_v2/simple_program_gevent.py | 34 +++ tests/profiling_v2/test_accuracy.py | 101 +++++++++ tests/profiling_v2/test_main.py | 227 ++++++++++++++++++++ tests/profiling_v2/test_profiler.py | 204 ++++++++++++++++++ tests/profiling_v2/test_scheduler.py | 54 +++++ 10 files changed, 850 insertions(+), 47 deletions(-) create mode 100755 tests/profiling_v2/simple_program.py create mode 100644 tests/profiling_v2/simple_program_fork.py create mode 100644 tests/profiling_v2/simple_program_gevent.py create mode 100644 tests/profiling_v2/test_accuracy.py create mode 100644 tests/profiling_v2/test_main.py create mode 100644 tests/profiling_v2/test_profiler.py create mode 100644 tests/profiling_v2/test_scheduler.py diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py index 690be4b183c..84f3ad60ea6 100644 --- a/tests/profiling/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -254,13 +254,12 @@ def test_ignore_profiler_single(): @pytest.mark.skipif(not TESTING_GEVENT, reason="Not testing gevent") -@pytest.mark.subprocess(ddtrace_run=True) +@pytest.mark.subprocess(ddtrace_run=True, env=dict(DD_PROFILING_IGNORE_PROFILER="1", DD_PROFILING_API_TIMEOUT="0.1")) def test_ignore_profiler_gevent_task(): import gevent.monkey gevent.monkey.patch_all() - import os import time from ddtrace.profiling import collector # noqa:F401 @@ -282,28 +281,22 @@ def collect(self): _fib(22) return [] - for ignore in (True, False): - os.environ["DD_PROFILING_API_TIMEOUT"] = "0.1" - os.environ["DD_PROFILING_IGNORE_PROFILER"] = str(ignore) - p = profiler.Profiler() - p.start() - # This test is particularly useful with gevent enabled: create a test collector that run often and for long - # we're sure to catch it with the StackProfiler and that it's not ignored. - c = CollectorTest(p._profiler._recorder, interval=0.00001) - c.start() + p = profiler.Profiler() + p.start() + # This test is particularly useful with gevent enabled: create a test collector that run often and for long + # we're sure to catch it with the StackProfiler and that it's not ignored. + c = CollectorTest(p._profiler._recorder, interval=0.00001) + c.start() - for _ in range(100): - events = p._profiler._recorder.reset() - ids = {e.task_id for e in events[stack_event.StackSampleEvent]} - if (c._worker.ident in ids) != str(ignore): - break - # Give some time for gevent to switch greenlets - time.sleep(0.1) - else: - raise AssertionError("ignore == " + ignore) + for _ in range(100): + events = p._profiler._recorder.reset() + ids = {e.task_id for e in events[stack_event.StackSampleEvent]} + if c._worker.ident in ids: + raise AssertionError("Collector thread found") + time.sleep(0.1) - c.stop() - p.stop(flush=False) + c.stop() + p.stop(flush=False) def test_collect(): diff --git a/tests/profiling/test_accuracy.py b/tests/profiling/test_accuracy.py index d5fcc030ef9..a332068e12b 100644 --- a/tests/profiling/test_accuracy.py +++ b/tests/profiling/test_accuracy.py @@ -31,16 +31,16 @@ def spend_16(): def spend_cpu_2(): - now = time.monotonic_ns() + now = time.process_time_ns() # Active wait for 2 seconds - while time.monotonic_ns() - now < 2e9: + while time.process_time_ns() - now < 2e9: pass def spend_cpu_3(): # Active wait for 3 seconds - now = time.monotonic_ns() - while time.monotonic_ns() - now < 3e9: + now = time.process_time_ns() + while time.process_time_ns() - now < 3e9: pass @@ -51,8 +51,12 @@ def spend_cpu_3(): CPU_TOLERANCE = 0.05 -def almost_equal(value, target, tolerance=TOLERANCE): - return abs(value - target) / target <= tolerance +def assert_almost_equal(value, target, tolerance=TOLERANCE): + if abs(value - target) / target > tolerance: + raise AssertionError( + f"Assertion failed: {value} is not approximately equal to {target} " + f"within tolerance={tolerance}, actual error={abs(value - target) / target}" + ) def total_time(time_data, funcname): @@ -66,7 +70,7 @@ def test_accuracy(): from ddtrace.profiling import profiler from ddtrace.profiling.collector import stack_event from tests.profiling.test_accuracy import CPU_TOLERANCE - from tests.profiling.test_accuracy import almost_equal + from tests.profiling.test_accuracy import assert_almost_equal from tests.profiling.test_accuracy import spend_16 from tests.profiling.test_accuracy import total_time @@ -85,20 +89,13 @@ def test_accuracy(): time_spent_ns[idx][frame[2]] += event.wall_time_ns cpu_spent_ns[idx][frame[2]] += event.cpu_time_ns - assert almost_equal(total_time(time_spent_ns, "spend_3"), 9e9) - assert almost_equal(total_time(time_spent_ns, "spend_1"), 2e9) - assert almost_equal(total_time(time_spent_ns, "spend_4"), 4e9) - assert almost_equal(total_time(time_spent_ns, "spend_16"), 16e9) - assert almost_equal(total_time(time_spent_ns, "spend_7"), 7e9) - - try: - from time import monotonic_ns # noqa:F401 - except ImportError: - # If we don't have access to high resolution clocks, we can't really test accurately things as it's spread in - # various Python implementation of monotonic, etc. - pass - else: - assert almost_equal(total_time(time_spent_ns, "spend_cpu_2"), 2e9) - assert almost_equal(total_time(time_spent_ns, "spend_cpu_3"), 3e9) - assert almost_equal(total_time(time_spent_ns, "spend_cpu_2"), 2e9, CPU_TOLERANCE) - assert almost_equal(total_time(time_spent_ns, "spend_cpu_3"), 3e9, CPU_TOLERANCE) + assert_almost_equal(total_time(time_spent_ns, "spend_3"), 9e9) + assert_almost_equal(total_time(time_spent_ns, "spend_1"), 2e9) + assert_almost_equal(total_time(time_spent_ns, "spend_4"), 4e9) + assert_almost_equal(total_time(time_spent_ns, "spend_16"), 16e9) + assert_almost_equal(total_time(time_spent_ns, "spend_7"), 7e9) + + assert_almost_equal(total_time(time_spent_ns, "spend_cpu_2"), 2e9) + assert_almost_equal(total_time(time_spent_ns, "spend_cpu_3"), 3e9) + assert_almost_equal(total_time(cpu_spent_ns, "spend_cpu_2"), 2e9, CPU_TOLERANCE) + assert_almost_equal(total_time(cpu_spent_ns, "spend_cpu_3"), 3e9, CPU_TOLERANCE) diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index af13a1ea237..74def22ed50 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -13,6 +13,7 @@ from ddtrace.profiling.collector import stack from ddtrace.settings.profiling import config from tests.profiling.collector import pprof_utils +from tests.profiling.collector import test_collector # Python 3.11.9 is not compatible with gevent, https://github.com/gevent/gevent/issues/2040 @@ -24,6 +25,43 @@ ) +# Use subprocess as ddup config persists across tests. +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_MAX_FRAMES="5", + DD_PROFILING_OUTPUT_PPROF="/tmp/test_collect_truncate", + DD_PROFILING_STACK_V2_ENABLED="1", + ) +) +@pytest.mark.skipif(sys.version_info[:2] == (3, 7), reason="stack_v2 is not supported on Python 3.7") +def test_collect_truncate(): + import os + + from ddtrace.profiling import profiler + from tests.profiling.collector import pprof_utils + from tests.profiling.collector.test_stack import func1 + + pprof_prefix = os.environ["DD_PROFILING_OUTPUT_PPROF"] + output_filename = pprof_prefix + "." + str(os.getpid()) + + max_nframes = int(os.environ["DD_PROFILING_MAX_FRAMES"]) + + p = profiler.Profiler() + p.start() + + func1() + + p.stop() + + profile = pprof_utils.parse_profile(output_filename) + samples = pprof_utils.get_samples_with_value_type(profile, "wall-time") + assert len(samples) > 0 + for sample in samples: + # stack v2 adds one extra frame for "%d frames omitted" message + # Also, it allows max_nframes + 1 frames, so we add 2 here. + assert len(sample.location_id) <= max_nframes + 2, len(sample.location_id) + + @pytest.mark.parametrize("stack_v2_enabled", [True, False]) def test_stack_locations(stack_v2_enabled, tmp_path): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: @@ -651,8 +689,23 @@ def _dofib(): assert checked_thread, "No samples found for the expected threads" +def test_max_time_usage(): + with pytest.raises(ValueError): + stack.StackCollector(None, max_time_usage_pct=0) + + +def test_max_time_usage_over(): + with pytest.raises(ValueError): + stack.StackCollector(None, max_time_usage_pct=200) + + @pytest.mark.parametrize( - ("stack_v2_enabled", "ignore_profiler"), [(True, True), (True, False), (False, True), (False, False)] + "stack_v2_enabled", + [True, False], +) +@pytest.mark.parametrize( + "ignore_profiler", + [True, False], ) def test_ignore_profiler(stack_v2_enabled, ignore_profiler, tmp_path): if sys.version_info[:2] == (3, 7) and stack_v2_enabled: @@ -691,3 +744,79 @@ def test_ignore_profiler(stack_v2_enabled, ignore_profiler, tmp_path): assert collector_worker_thread_id in thread_ids else: assert collector_worker_thread_id not in thread_ids + + +# TODO: support ignore profiler with stack_v2 and update this test +@pytest.mark.skipif(not TESTING_GEVENT, reason="Not testing gevent") +@pytest.mark.subprocess( + ddtrace_run=True, + env=dict(DD_PROFILING_IGNORE_PROFILER="1", DD_PROFILING_OUTPUT_PPROF="/tmp/test_ignore_profiler_gevent_task"), +) +def test_ignore_profiler_gevent_task(): + import gevent.monkey + + gevent.monkey.patch_all() + + import os + import time + import typing + + from ddtrace.profiling import collector + from ddtrace.profiling import event as event_mod + from ddtrace.profiling import profiler + from ddtrace.profiling.collector import stack + from tests.profiling.collector import pprof_utils + + def _fib(n): + if n == 1: + return 1 + elif n == 0: + return 0 + else: + return _fib(n - 1) + _fib(n - 2) + + class CollectorTest(collector.PeriodicCollector): + def collect(self) -> typing.Iterable[typing.Iterable[event_mod.Event]]: + _fib(22) + return [] + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + + p = profiler.Profiler() + + p.start() + + for c in p._profiler._collectors: + if isinstance(c, stack.StackCollector): + c.ignore_profiler + + c = CollectorTest(None, interval=0.00001) + c.start() + + time.sleep(3) + + worker_ident = c._worker.ident + + c.stop() + p.stop() + + profile = pprof_utils.parse_profile(output_filename + "." + str(os.getpid())) + + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + + thread_ids = set() + for sample in samples: + thread_id_label = pprof_utils.get_label_with_key(profile.string_table, sample, "thread id") + thread_id = int(thread_id_label.num) + thread_ids.add(thread_id) + + assert worker_ident not in thread_ids + + +def test_repr(): + test_collector._test_repr( + stack.StackCollector, + "StackCollector(status=, " + "recorder=Recorder(default_max_events=16384, max_events={}), min_interval_time=0.01, max_time_usage_pct=1.0, " + "nframes=64, ignore_profiler=False, endpoint_collection_enabled=None, tracer=None)", + ) diff --git a/tests/profiling_v2/simple_program.py b/tests/profiling_v2/simple_program.py new file mode 100755 index 00000000000..ed07bc5a402 --- /dev/null +++ b/tests/profiling_v2/simple_program.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +import os +import sys +import time + +from ddtrace.internal import service +from ddtrace.profiling import bootstrap +from ddtrace.profiling.collector import stack + + +for running_collector in bootstrap.profiler._profiler._collectors: + if isinstance(running_collector, stack.StackCollector): + break +else: + raise AssertionError("Unable to find stack collector") + + +print("hello world") +assert running_collector.status == service.ServiceStatus.RUNNING +print(running_collector.interval) + +t0 = time.time() +while time.time() - t0 < (running_collector.interval * 10): + pass + +# Do some serious memory allocations! +for _ in range(5000000): + object() + +print(os.getpid()) +print(bootstrap.profiler._profiler._stack_v2_enabled) +sys.exit(42) diff --git a/tests/profiling_v2/simple_program_fork.py b/tests/profiling_v2/simple_program_fork.py new file mode 100644 index 00000000000..ad8c0541ccd --- /dev/null +++ b/tests/profiling_v2/simple_program_fork.py @@ -0,0 +1,32 @@ +import os +import sys +import threading + +from ddtrace.internal import service +import ddtrace.profiling.auto +import ddtrace.profiling.bootstrap +import ddtrace.profiling.profiler + + +lock = threading.Lock() +lock.acquire() + + +assert ddtrace.profiling.bootstrap.profiler.status == service.ServiceStatus.RUNNING + + +child_pid = os.fork() +if child_pid == 0: + # Release it + lock.release() + + # We track this one though + lock = threading.Lock() + lock.acquire() + lock.release() +else: + lock.release() + assert ddtrace.profiling.bootstrap.profiler.status == service.ServiceStatus.RUNNING + print(child_pid) + pid, status = os.waitpid(child_pid, 0) + sys.exit(os.WEXITSTATUS(status)) diff --git a/tests/profiling_v2/simple_program_gevent.py b/tests/profiling_v2/simple_program_gevent.py new file mode 100644 index 00000000000..f50fa3aa2e0 --- /dev/null +++ b/tests/profiling_v2/simple_program_gevent.py @@ -0,0 +1,34 @@ +# Import from ddtrace before monkey patching to ensure that we grab all the +# necessary references to the unpatched modules. +import ddtrace.auto # noqa: F401, I001 +import ddtrace.profiling.auto # noqa:F401 + + +import gevent.monkey # noqa:F402 + +gevent.monkey.patch_all() + +import threading # noqa: E402, F402, I001 +import time # noqa: E402, F402 + + +def fibonacci(n): + if n == 0: + return 0 + elif n == 1: + return 1 + else: + return fibonacci(n - 1) + fibonacci(n - 2) + + +i = 1 +for _ in range(20): + threads = [] + for _ in range(10): + t = threading.Thread(target=fibonacci, args=(i,)) + t.start() + threads.append(t) + i += 1 + for t in threads: + t.join() + time.sleep(0.1) diff --git a/tests/profiling_v2/test_accuracy.py b/tests/profiling_v2/test_accuracy.py new file mode 100644 index 00000000000..61fbe3322ff --- /dev/null +++ b/tests/profiling_v2/test_accuracy.py @@ -0,0 +1,101 @@ +# -*- encoding: utf-8 -*- +import sys + +import pytest + + +@pytest.mark.subprocess( + env=dict(DD_PROFILING_MAX_TIME_USAGE_PCT="100", DD_PROFILING_OUTPUT_PPROF="/tmp/test_accuracy_libdd.pprof") +) +def test_accuracy_libdd(): + import collections + import os + + from ddtrace.profiling import profiler + from tests.profiling.collector import pprof_utils + from tests.profiling.test_accuracy import assert_almost_equal + from tests.profiling.test_accuracy import spend_16 + + # Set this to 100 so we don't sleep too often and mess with the precision. + p = profiler.Profiler() + p.start() + spend_16() + p.stop() + wall_times = collections.defaultdict(lambda: 0) + cpu_times = collections.defaultdict(lambda: 0) + profile = pprof_utils.parse_profile(os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid())) + + for sample in profile.sample: + wall_time_index = pprof_utils.get_sample_type_index(profile, "wall-time") + + wall_time_spent_ns = sample.value[wall_time_index] + cpu_time_index = pprof_utils.get_sample_type_index(profile, "cpu-time") + cpu_time_spent_ns = sample.value[cpu_time_index] + + for location_id in sample.location_id: + location = pprof_utils.get_location_with_id(profile, location_id) + line = location.line[0] + function = pprof_utils.get_function_with_id(profile, line.function_id) + function_name = profile.string_table[function.name] + wall_times[function_name] += wall_time_spent_ns + cpu_times[function_name] += cpu_time_spent_ns + + assert_almost_equal(wall_times["spend_3"], 9e9) + assert_almost_equal(wall_times["spend_1"], 2e9) + assert_almost_equal(wall_times["spend_4"], 4e9) + assert_almost_equal(wall_times["spend_16"], 16e9) + assert_almost_equal(wall_times["spend_7"], 7e9) + + assert_almost_equal(wall_times["spend_cpu_2"], 2e9, tolerance=0.07) + assert_almost_equal(wall_times["spend_cpu_3"], 3e9, tolerance=0.07) + assert_almost_equal(cpu_times["spend_cpu_2"], 2e9, tolerance=0.07) + assert_almost_equal(cpu_times["spend_cpu_3"], 3e9, tolerance=0.07) + + +@pytest.mark.subprocess( + env=dict(DD_PROFILING_STACK_V2_ENABLED="1", DD_PROFILING_OUTPUT_PPROF="/tmp/test_accuracy_stack_v2.pprof") +) +@pytest.mark.skipif(sys.version_info[:2] == (3, 7), reason="stack_v2 is not supported on Python 3.7") +def test_accuracy_stack_v2(): + import collections + import os + + from ddtrace.profiling import profiler + from tests.profiling.collector import pprof_utils + from tests.profiling.test_accuracy import assert_almost_equal + from tests.profiling.test_accuracy import spend_16 + + # Set this to 100 so we don't sleep too often and mess with the precision. + p = profiler.Profiler() + p.start() + spend_16() + p.stop() + wall_times = collections.defaultdict(lambda: 0) + cpu_times = collections.defaultdict(lambda: 0) + profile = pprof_utils.parse_profile(os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid())) + + for sample in profile.sample: + wall_time_index = pprof_utils.get_sample_type_index(profile, "wall-time") + + wall_time_spent_ns = sample.value[wall_time_index] + cpu_time_index = pprof_utils.get_sample_type_index(profile, "cpu-time") + cpu_time_spent_ns = sample.value[cpu_time_index] + + for location_id in sample.location_id: + location = pprof_utils.get_location_with_id(profile, location_id) + line = location.line[0] + function = pprof_utils.get_function_with_id(profile, line.function_id) + function_name = profile.string_table[function.name] + wall_times[function_name] += wall_time_spent_ns + cpu_times[function_name] += cpu_time_spent_ns + + assert_almost_equal(wall_times["spend_3"], 9e9) + assert_almost_equal(wall_times["spend_1"], 2e9) + assert_almost_equal(wall_times["spend_4"], 4e9) + assert_almost_equal(wall_times["spend_16"], 16e9) + assert_almost_equal(wall_times["spend_7"], 7e9) + + assert_almost_equal(wall_times["spend_cpu_2"], 2e9, tolerance=0.07) + assert_almost_equal(wall_times["spend_cpu_3"], 3e9, tolerance=0.07) + assert_almost_equal(cpu_times["spend_cpu_2"], 2e9, tolerance=0.07) + assert_almost_equal(cpu_times["spend_cpu_3"], 3e9, tolerance=0.07) diff --git a/tests/profiling_v2/test_main.py b/tests/profiling_v2/test_main.py new file mode 100644 index 00000000000..3142a1fbba8 --- /dev/null +++ b/tests/profiling_v2/test_main.py @@ -0,0 +1,227 @@ +# -*- encoding: utf-8 -*- +import multiprocessing +import os +import sys + +import pytest + +from tests.profiling.collector import lock_utils +from tests.profiling.collector import pprof_utils +from tests.utils import call_program +from tests.utils import flaky + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_call_script(stack_v2_enabled): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + env = os.environ.copy() + env["DD_PROFILING_ENABLED"] = "1" + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, _ = call_program( + "ddtrace-run", sys.executable, os.path.join(os.path.dirname(__file__), "simple_program.py"), env=env + ) + if sys.platform == "win32": + assert exitcode == 0, (stdout, stderr) + else: + assert exitcode == 42, (stdout, stderr) + hello, interval, pid, stack_v2 = list(s.strip() for s in stdout.decode().strip().split("\n")) + assert hello == "hello world", stdout.decode().strip() + assert float(interval) >= 0.01, stdout.decode().strip() + assert stack_v2 == str(stack_v2_enabled) + + +@pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_call_script_gevent(stack_v2_enabled): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + if sys.version_info[:2] == (3, 8) and stack_v2_enabled: + pytest.skip("this test is flaky on 3.8 with stack v2") + env = os.environ.copy() + env["DD_PROFILING_ENABLED"] = "1" + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, pid = call_program( + sys.executable, os.path.join(os.path.dirname(__file__), "simple_program_gevent.py"), env=env + ) + assert exitcode == 0, (stdout, stderr) + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +def test_call_script_pprof_output(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + """This checks if the pprof output and atexit register work correctly. + + The script does not run for one minute, so if the `stop_on_exit` flag is broken, this test will fail. + """ + filename = str(tmp_path / "pprof") + env = os.environ.copy() + env["DD_PROFILING_OUTPUT_PPROF"] = filename + env["DD_PROFILING_CAPTURE_PCT"] = "1" + env["DD_PROFILING_ENABLED"] = "1" + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, _ = call_program( + "ddtrace-run", + sys.executable, + os.path.join(os.path.dirname(__file__), "../profiling", "simple_program.py"), + env=env, + ) + if sys.platform == "win32": + assert exitcode == 0, (stdout, stderr) + else: + assert exitcode == 42, (stdout, stderr) + _, _, _, pid = list(s.strip() for s in stdout.decode().strip().split("\n")) + profile = pprof_utils.parse_profile(filename + "." + str(pid)) + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + assert len(samples) > 0 + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") +def test_fork(stack_v2_enabled, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + + filename = str(tmp_path / "pprof") + env = os.environ.copy() + env["DD_PROFILING_OUTPUT_PPROF"] = filename + env["DD_PROFILING_CAPTURE_PCT"] = "100" + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, pid = call_program( + "python", os.path.join(os.path.dirname(__file__), "simple_program_fork.py"), env=env + ) + assert exitcode == 0 + child_pid = stdout.decode().strip() + profile = pprof_utils.parse_profile(filename + "." + str(pid)) + pprof_utils.assert_lock_events( + profile, + expected_acquire_events=[ + pprof_utils.LockAcquireEvent( + caller_name="", + filename="simple_program_fork.py", + linenos=lock_utils.LineNo(create=11, acquire=12, release=28), + lock_name="lock", + ), + ], + expected_release_events=[ + pprof_utils.LockReleaseEvent( + caller_name="", + filename="simple_program_fork.py", + linenos=lock_utils.LineNo(create=11, acquire=12, release=28), + lock_name="lock", + ), + ], + ) + child_profile = pprof_utils.parse_profile(filename + "." + str(child_pid)) + pprof_utils.assert_lock_events( + child_profile, + expected_acquire_events=[ + # After fork(), we clear the samples in child, so we only have one + # lock acquire event + pprof_utils.LockAcquireEvent( + caller_name="", + filename="simple_program_fork.py", + linenos=lock_utils.LineNo(create=24, acquire=25, release=26), + lock_name="lock", + ), + ], + expected_release_events=[ + pprof_utils.LockReleaseEvent( + caller_name="", + filename="simple_program_fork.py", + linenos=lock_utils.LineNo(create=11, acquire=12, release=21), + lock_name="lock", + ), + pprof_utils.LockReleaseEvent( + caller_name="", + filename="simple_program_fork.py", + linenos=lock_utils.LineNo(create=24, acquire=25, release=26), + lock_name="lock", + ), + ], + ) + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") +@pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") +def test_fork_gevent(stack_v2_enabled): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + env = os.environ.copy() + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, pid = call_program( + "python", os.path.join(os.path.dirname(__file__), "../profiling", "gevent_fork.py"), env=env + ) + assert exitcode == 0 + + +methods = multiprocessing.get_all_start_methods() + + +@pytest.mark.parametrize("stack_v2_enabled", [True, False]) +@pytest.mark.parametrize( + "method", + set(methods) - {"forkserver", "fork"}, +) +def test_multiprocessing(stack_v2_enabled, method, tmp_path): + if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + pytest.skip("stack_v2 is not supported on Python 3.7") + filename = str(tmp_path / "pprof") + env = os.environ.copy() + env["DD_PROFILING_OUTPUT_PPROF"] = filename + env["DD_PROFILING_ENABLED"] = "1" + env["DD_PROFILING_CAPTURE_PCT"] = "1" + env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" + stdout, stderr, exitcode, _ = call_program( + "ddtrace-run", + sys.executable, + os.path.join(os.path.dirname(__file__), "../profiling", "_test_multiprocessing.py"), + method, + env=env, + ) + assert exitcode == 0, (stdout, stderr) + pid, child_pid = list(s.strip() for s in stdout.decode().strip().split("\n")) + profile = pprof_utils.parse_profile(filename + "." + str(pid)) + samples = pprof_utils.get_samples_with_value_type(profile, "cpu-time") + assert len(samples) > 0 + child_profile = pprof_utils.parse_profile(filename + "." + str(child_pid)) + child_samples = pprof_utils.get_samples_with_value_type(child_profile, "cpu-time") + assert len(child_samples) > 0 + + +@flaky(1731959126) # Marking as flaky so it will show up in flaky reports +@pytest.mark.skipif(os.environ.get("GITLAB_CI") == "true", reason="Hanging and failing in GitLab CI") +@pytest.mark.subprocess( + ddtrace_run=True, + env=dict(DD_PROFILING_ENABLED="1"), + err=lambda _: "RuntimeError: the memalloc module is already started" not in _, +) +def test_memalloc_no_init_error_on_fork(): + import os + + pid = os.fork() + if not pid: + exit(0) + os.waitpid(pid, 0) + + +# Not parametrizing with stack_v2_enabled as subprocess mark doesn't support +# parametrized tests and this only tests our start up code. +@pytest.mark.subprocess( + ddtrace_run=True, + env=dict( + DD_PROFILING_ENABLED="1", + DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE="1", + ), + out="OK\n", + err=None, +) +def test_profiler_start_up_with_module_clean_up_in_protobuf_app(): + # This can cause segfaults if we do module clean up with later versions of + # protobuf. This is a regression test. + from google.protobuf import empty_pb2 # noqa:F401 + + print("OK") diff --git a/tests/profiling_v2/test_profiler.py b/tests/profiling_v2/test_profiler.py new file mode 100644 index 00000000000..b5a2bb4bae8 --- /dev/null +++ b/tests/profiling_v2/test_profiler.py @@ -0,0 +1,204 @@ +import logging +import sys +import time + +import mock +import pytest + +import ddtrace +from ddtrace.profiling import collector +from ddtrace.profiling import exporter +from ddtrace.profiling import profiler +from ddtrace.profiling import scheduler +from ddtrace.profiling.collector import asyncio +from ddtrace.profiling.collector import stack +from ddtrace.profiling.collector import threading + + +def test_status(): + p = profiler.Profiler() + assert repr(p.status) == "" + p.start() + assert repr(p.status) == "" + p.stop(flush=False) + assert repr(p.status) == "" + + +def test_restart(): + p = profiler.Profiler() + p.start() + p.stop(flush=False) + p.start() + p.stop(flush=False) + + +def test_multiple_stop(): + """Check that the profiler can be stopped twice.""" + p = profiler.Profiler() + p.start() + p.stop(flush=False) + p.stop(flush=False) + + +def test_tracer_api(monkeypatch): + monkeypatch.setenv("DD_API_KEY", "foobar") + prof = profiler.Profiler(tracer=ddtrace.tracer) + assert prof.tracer == ddtrace.tracer + for col in prof._profiler._collectors: + if isinstance(col, stack.StackCollector): + assert col.tracer == ddtrace.tracer + break + else: + pytest.fail("Unable to find stack collector") + + +def test_profiler_init_float_division_regression(run_python_code_in_subprocess): + """ + Regression test for https://github.com/DataDog/dd-trace-py/pull/3751 + When float division is enabled, the value of `max_events` can be a `float`, + this is then passed as `deque(maxlen=float)` which is a type error + + File "/var/task/ddtrace/profiling/recorder.py", line 80, in _get_deque_for_event_type + return collections.deque(maxlen=self.max_events.get(event_type, self.default_max_events)) + TypeError: an integer is required + """ + code = """ +from ddtrace.profiling import profiler +from ddtrace.profiling.collector import stack_event + +prof = profiler.Profiler() + +# The error only happened for this specific kind of event +# DEV: Yes, this is likely a brittle way to test, but quickest/easiest way to trigger the error +prof._recorder.push_event(stack_event.StackExceptionSampleEvent()) + """ + + out, err, status, _ = run_python_code_in_subprocess(code) + assert status == 0, err + assert out == b"", err + assert err == b"" + + +@pytest.mark.subprocess() +def test_default_memory(): + from ddtrace.profiling import profiler + from ddtrace.profiling.collector import memalloc + + assert any(isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) + + +@pytest.mark.subprocess(env=dict(DD_PROFILING_MEMORY_ENABLED="true")) +def test_enable_memory(): + from ddtrace.profiling import profiler + from ddtrace.profiling.collector import memalloc + + assert any(isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) + + +@pytest.mark.subprocess(env=dict(DD_PROFILING_MEMORY_ENABLED="false")) +def test_disable_memory(): + from ddtrace.profiling import profiler + from ddtrace.profiling.collector import memalloc + + assert all(not isinstance(col, memalloc.MemoryCollector) for col in profiler.Profiler()._profiler._collectors) + + +def test_copy(): + p = profiler._ProfilerInstance(env="123", version="dwq", service="foobar") + c = p.copy() + assert c == p + assert p.env == c.env + assert p.version == c.version + assert p.service == c.service + assert p.tracer == c.tracer + assert p.tags == c.tags + + +def test_failed_start_collector(caplog, monkeypatch): + class ErrCollect(collector.Collector): + def _start_service(self): + raise RuntimeError("could not import required module") + + def _stop_service(self): + pass + + @staticmethod + def collect(): + pass + + @staticmethod + def snapshot(): + raise Exception("error!") + + monkeypatch.setenv("DD_PROFILING_UPLOAD_INTERVAL", "1") + + class Exporter(exporter.Exporter): + def export(self, events, *args, **kwargs): + pass + + class TestProfiler(profiler._ProfilerInstance): + def _build_default_exporters(self, *args, **kargs): + return [Exporter()] + + p = TestProfiler() + err_collector = mock.MagicMock(wraps=ErrCollect(p._recorder)) + p._collectors = [err_collector] + p.start() + + def profiling_tuples(tuples): + return [t for t in tuples if t[0].startswith("ddtrace.profiling")] + + assert profiling_tuples(caplog.record_tuples) == [ + ("ddtrace.profiling.profiler", logging.ERROR, "Failed to start collector %r, disabling." % err_collector) + ] + time.sleep(2) + p.stop() + assert err_collector.snapshot.call_count == 0 + assert profiling_tuples(caplog.record_tuples) == [ + ("ddtrace.profiling.profiler", logging.ERROR, "Failed to start collector %r, disabling." % err_collector) + ] + + +def test_default_collectors(): + p = profiler.Profiler() + assert any(isinstance(c, stack.StackCollector) for c in p._profiler._collectors) + assert any(isinstance(c, threading.ThreadingLockCollector) for c in p._profiler._collectors) + try: + import asyncio as _ # noqa: F401 + except ImportError: + pass + else: + assert any(isinstance(c, asyncio.AsyncioLockCollector) for c in p._profiler._collectors) + p.stop(flush=False) + + +def test_profiler_serverless(monkeypatch): + # type: (...) -> None + monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "foobar") + p = profiler.Profiler() + assert isinstance(p._scheduler, scheduler.ServerlessScheduler) + assert p.tags["functionname"] == "foobar" + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.7 deprecation warning") +@pytest.mark.subprocess() +def test_profiler_ddtrace_deprecation(): + """ + ddtrace interfaces loaded by the profiler can be marked deprecated, and we should update + them when this happens. As reported by https://github.com/DataDog/dd-trace-py/issues/8881 + """ + import warnings + + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + from ddtrace.profiling import _threading # noqa:F401 + from ddtrace.profiling import event # noqa:F401 + from ddtrace.profiling import profiler # noqa:F401 + from ddtrace.profiling import recorder # noqa:F401 + from ddtrace.profiling import scheduler # noqa:F401 + from ddtrace.profiling.collector import _lock # noqa:F401 + from ddtrace.profiling.collector import _task # noqa:F401 + from ddtrace.profiling.collector import _traceback # noqa:F401 + from ddtrace.profiling.collector import memalloc # noqa:F401 + from ddtrace.profiling.collector import stack # noqa:F401 + from ddtrace.profiling.collector import stack_event # noqa:F401 diff --git a/tests/profiling_v2/test_scheduler.py b/tests/profiling_v2/test_scheduler.py new file mode 100644 index 00000000000..dc3c2c0d7d1 --- /dev/null +++ b/tests/profiling_v2/test_scheduler.py @@ -0,0 +1,54 @@ +# -*- encoding: utf-8 -*- +import logging +import time + +import mock + +from ddtrace.profiling import exporter +from ddtrace.profiling import scheduler + + +def test_thread_name(): + exp = exporter.NullExporter() + s = scheduler.Scheduler(None, [exp]) + s.start() + assert s._worker.name == "ddtrace.profiling.scheduler:Scheduler" + s.stop() + + +def test_before_flush(): + x = {} + + def call_me(): + x["OK"] = True + + s = scheduler.Scheduler(None, [exporter.NullExporter()], before_flush=call_me) + s.flush() + assert x["OK"] + + +def test_before_flush_failure(caplog): + def call_me(): + raise Exception("LOL") + + s = scheduler.Scheduler(None, [exporter.NullExporter()], before_flush=call_me) + s.flush() + assert caplog.record_tuples == [ + (("ddtrace.profiling.scheduler", logging.ERROR, "Scheduler before_flush hook failed")) + ] + + +@mock.patch("ddtrace.profiling.scheduler.Scheduler.periodic") +def test_serverless_periodic(mock_periodic): + s = scheduler.ServerlessScheduler(None, [exporter.NullExporter()]) + # Fake start() + s._last_export = time.time_ns() + s.periodic() + assert s._profiled_intervals == 1 + mock_periodic.assert_not_called() + s._last_export = time.time_ns() - 65 + s._profiled_intervals = 65 + s.periodic() + assert s._profiled_intervals == 0 + assert s.interval == 1 + mock_periodic.assert_called() From b253aa32cc1b24650a34b4d5083661706b651c1f Mon Sep 17 00:00:00 2001 From: Thomas Hunter II Date: Fri, 20 Dec 2024 12:49:46 -0800 Subject: [PATCH 359/372] docs: fix create issue screen: rename config.yaml -> yml (#11816) - the [create issue](https://github.com/DataDog/dd-trace-py/issues/new/choose) screen has issue templates but no external links - the only difference I can see is that I used config.yaml whereas in other repos it's config.yml - very confusing since the issue templates use .yaml... - follow up to #11765 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/ISSUE_TEMPLATE/{config.yaml => config.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{config.yaml => config.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yaml rename to .github/ISSUE_TEMPLATE/config.yml From 58ef74f405f3e9e4c24f31cccd6d6330e14d3c97 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 23 Dec 2024 08:19:39 +0100 Subject: [PATCH 360/372] chore(iast): memory leak in aspect modulo (#11787) a memory leak was introduced in #11601 This PR reverts it. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_taint_tracking/Aspects/AspectModulo.cpp | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp index b7454de26f8..a08f76d9f3d 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectModulo.cpp @@ -2,7 +2,7 @@ #include "Helpers.h" static PyObject* -do_modulo(PyObject* text, PyObject* insert_tuple_or_obj, py::object py_candidate_text, py::object py_candidate_tuple) +do_modulo(PyObject* text, PyObject* insert_tuple_or_obj) { PyObject* result = nullptr; @@ -13,22 +13,18 @@ do_modulo(PyObject* text, PyObject* insert_tuple_or_obj, py::object py_candidate Py_INCREF(insert_tuple); } else { insert_tuple = PyTuple_Pack(1, insert_tuple_or_obj); + if (insert_tuple == nullptr) { + return nullptr; + } } - if (PyUnicode_Check(text) && insert_tuple != nullptr) { + if (PyUnicode_Check(text)) { result = PyUnicode_Format(text, insert_tuple); + } else if (PyBytes_Check(text) or PyByteArray_Check(text)) { + auto method_name = PyUnicode_FromString("__mod__"); + result = PyObject_CallMethodObjArgs(text, method_name, insert_tuple, nullptr); + Py_DECREF(method_name); } else { - try { - py::object res_py = py_candidate_text.attr("__mod__")(py_candidate_tuple); - PyObject* res_pyo = res_py.ptr(); - if (res_pyo != nullptr) { - Py_INCREF(res_pyo); - } - return res_pyo; - } catch (py::error_already_set& e) { - e.restore(); - return nullptr; - } } Py_DECREF(insert_tuple); if (has_pyerr()) { @@ -53,7 +49,21 @@ api_modulo_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) // Lambda to get the result of the modulo operation auto get_result = [&]() -> PyObject* { - return do_modulo(candidate_text, candidate_tuple, py_candidate_text, py_candidate_tuple); + PyObject* res = do_modulo(candidate_text, candidate_tuple); + if (res == nullptr) { + try { + py::object res_py = py_candidate_text.attr("__mod__")(py_candidate_tuple); + PyObject* res_pyo = res_py.ptr(); + if (res_pyo != nullptr) { + Py_INCREF(res_pyo); + } + return res_pyo; + } catch (py::error_already_set& e) { + e.restore(); + return nullptr; + } + } + return res; }; TRY_CATCH_ASPECT("modulo_aspect", return get_result(), , { @@ -97,10 +107,7 @@ api_modulo_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) } py::tuple formatted_parameters(list_formatted_parameters); - PyObject* applied_params = do_modulo(StringToPyObject(fmttext, py_str_type).ptr(), - formatted_parameters.ptr(), - StringToPyObject(fmttext, py_str_type), - formatted_parameters); + PyObject* applied_params = do_modulo(StringToPyObject(fmttext, py_str_type).ptr(), formatted_parameters.ptr()); if (applied_params == nullptr) { return get_result(); } From d46206c8214c2d04500dc34187926370f83df184 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 23 Dec 2024 15:03:26 +0100 Subject: [PATCH 361/372] chore(iast): move inner funcions (#11570) Remove inner functions from IAST code ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{d57247c.txt => 10f3c55.txt} | 68 ++-- .../requirements/{2848d2c.txt => 1532dd6.txt} | 19 +- .../requirements/{1ef9f39.txt => 1655cb3.txt} | 63 ++-- .../requirements/{d5fcd88.txt => 1aa41b2.txt} | 19 +- .../requirements/{16ae097.txt => 1c5581b.txt} | 68 ++-- .../requirements/{1d3001d.txt => 1f06d17.txt} | 63 ++-- .../requirements/{11065bb.txt => 4de03a5.txt} | 62 ++-- .../requirements/{cbc433f.txt => 556d901.txt} | 65 ++-- .../requirements/{1fe2c8e.txt => b6e9905.txt} | 62 ++-- benchmarks/appsec_iast_aspects/scenario.py | 4 +- ddtrace/appsec/__init__.py | 2 +- ddtrace/appsec/_asm_request_context.py | 16 +- ddtrace/appsec/_common_module_patches.py | 13 +- ddtrace/appsec/_iast/_handlers.py | 43 +-- ddtrace/appsec/_iast/_iast_request_context.py | 32 +- ddtrace/appsec/_iast/_listener.py | 28 ++ ddtrace/appsec/_iast/_patch.py | 19 +- .../appsec/_iast/_patches/json_tainting.py | 4 +- .../appsec/_iast/_taint_tracking/__init__.py | 309 +++--------------- .../appsec/_iast/_taint_tracking/_context.py | 10 + .../appsec/_iast/_taint_tracking/_debug.py | 57 ++++ .../appsec/_iast/_taint_tracking/_errors.py | 16 + .../appsec/_iast/_taint_tracking/_native.cpp | 13 - .../_iast/_taint_tracking/_taint_objects.py | 154 +++++++++ .../appsec/_iast/_taint_tracking/aspects.py | 65 ++-- ddtrace/appsec/_iast/_taint_utils.py | 13 +- ddtrace/appsec/_iast/_utils.py | 4 +- ddtrace/appsec/_iast/reporter.py | 2 +- .../_iast/taint_sinks/command_injection.py | 20 +- .../_iast/taint_sinks/header_injection.py | 28 +- .../_iast/taint_sinks/insecure_cookie.py | 2 +- .../_iast/taint_sinks/path_traversal.py | 15 +- ddtrace/appsec/_iast/taint_sinks/ssrf.py | 16 +- ddtrace/contrib/internal/langchain/patch.py | 8 +- riotfile.py | 1 + scripts/iast/leak_functions.py | 2 +- scripts/iast/mod_leak_functions.py | 4 +- scripts/iast/test_references.py | 6 +- tests/appsec/app.py | 56 ++-- tests/appsec/iast/aspects/aspect_utils.py | 2 +- tests/appsec/iast/aspects/test_add_aspect.py | 10 +- .../iast/aspects/test_add_aspect_fixtures.py | 4 +- .../iast/aspects/test_add_inplace_aspect.py | 6 +- .../test_add_inplace_aspect_fixtures.py | 4 +- tests/appsec/iast/aspects/test_asyncio.py | 6 +- .../aspects/test_bytearray_extend_aspect.py | 8 +- .../aspects/test_common_replace_aspects.py | 4 +- .../iast/aspects/test_encode_decode_aspect.py | 4 +- .../aspects/test_format_aspect_fixtures.py | 8 +- .../aspects/test_index_aspect_fixtures.py | 8 +- tests/appsec/iast/aspects/test_io_aspects.py | 6 +- .../iast/aspects/test_join_aspect_fixtures.py | 8 +- .../aspects/test_modulo_aspect_fixtures.py | 4 +- .../iast/aspects/test_ospath_aspects.py | 4 +- .../aspects/test_ospath_aspects_fixtures.py | 8 +- .../iast/aspects/test_other_patching.py | 4 +- tests/appsec/iast/aspects/test_re_aspects.py | 6 +- .../iast/aspects/test_replace_aspect.py | 4 +- .../appsec/iast/aspects/test_side_effects.py | 6 +- .../aspects/test_slice_aspect_fixtures.py | 8 +- .../appsec/iast/aspects/test_split_aspect.py | 6 +- tests/appsec/iast/aspects/test_str_aspect.py | 6 +- .../appsec/iast/fixtures/entrypoint/views.py | 6 +- .../taint_sinks/sql_injection_psycopg2.py | 4 +- .../taint_sinks/sql_injection_sqlalchemy.py | 4 +- .../taint_sinks/sql_injection_sqlite3.py | 4 +- .../taint_sinks/test_command_injection.py | 4 +- .../test_command_injection_redacted.py | 2 +- .../test_header_injection_redacted.py | 4 +- .../iast/taint_sinks/test_path_traversal.py | 2 +- .../test_path_traversal_redacted.py | 2 +- .../iast/taint_sinks/test_sql_injection.py | 4 +- .../test_sql_injection_redacted.py | 4 +- tests/appsec/iast/taint_sinks/test_ssrf.py | 2 +- .../iast/taint_sinks/test_ssrf_redacted.py | 2 +- .../taint_tracking/test_native_taint_range.py | 12 +- .../taint_tracking/test_taint_tracking.py | 4 +- tests/appsec/iast/test_env_var.py | 7 - tests/appsec/iast/test_grpc_iast.py | 2 +- .../appsec/iast/test_iast_propagation_path.py | 2 +- tests/appsec/iast/test_json_tainting.py | 6 +- tests/appsec/iast/test_taint_utils.py | 4 +- tests/appsec/iast/test_telemetry.py | 2 +- .../iast_memcheck/test_iast_mem_check.py | 8 +- .../iast_packages/packages/pkg_attrs.py | 2 +- .../packages/pkg_beautifulsoup4.py | 2 +- .../iast_packages/packages/pkg_cachetools.py | 2 +- .../packages/pkg_chartset_normalizer.py | 2 +- .../packages/pkg_cryptography.py | 2 +- .../iast_packages/packages/pkg_docutils.py | 2 +- .../packages/pkg_exceptiongroup.py | 2 +- .../appsec/iast_packages/packages/pkg_idna.py | 2 +- .../iast_packages/packages/pkg_iniconfig.py | 2 +- .../iast_packages/packages/pkg_jinja2.py | 2 +- .../appsec/iast_packages/packages/pkg_lxml.py | 2 +- .../iast_packages/packages/pkg_multidict.py | 2 +- .../packages/pkg_platformdirs.py | 2 +- .../iast_packages/packages/pkg_pyasn1.py | 2 +- .../iast_packages/packages/pkg_pygments.py | 2 +- .../iast_packages/packages/pkg_pynacl.py | 2 +- .../iast_packages/packages/pkg_pyparsing.py | 2 +- .../packages/pkg_python_multipart.py | 2 +- .../iast_packages/packages/pkg_pyyaml.py | 2 +- .../appsec/iast_packages/packages/pkg_rsa.py | 2 +- .../iast_packages/packages/pkg_soupsieve.py | 2 +- .../iast_packages/packages/pkg_sqlalchemy.py | 2 +- .../iast_packages/packages/pkg_tomli.py | 2 +- .../iast_packages/packages/pkg_wrapt.py | 2 +- .../appsec/iast_packages/packages/pkg_yarl.py | 2 +- tests/appsec/iast_packages/packages/utils.py | 2 +- .../iast_tdd_propagation/flask_orm_app.py | 2 +- .../flask_propagation_views.py | 2 +- .../flask_taint_sinks_views.py | 2 +- .../integrations/pygoat_tests/test_pygoat.py | 4 +- .../integrations/test_flask_iast_patching.py | 2 +- tests/appsec/integrations/test_langchain.py | 2 +- tests/appsec/integrations/test_psycopg2.py | 2 +- tests/contrib/dbapi/test_dbapi_appsec.py | 6 +- .../contrib/django/django_app/appsec_urls.py | 21 +- .../contrib/django/test_django_appsec_iast.py | 10 +- .../fastapi/test_fastapi_appsec_iast.py | 38 +-- tests/contrib/flask/test_flask_appsec_iast.py | 42 +-- tests/smoke_test.py | 10 +- 123 files changed, 905 insertions(+), 966 deletions(-) rename .riot/requirements/{d57247c.txt => 10f3c55.txt} (59%) rename .riot/requirements/{2848d2c.txt => 1532dd6.txt} (83%) rename .riot/requirements/{1ef9f39.txt => 1655cb3.txt} (60%) rename .riot/requirements/{d5fcd88.txt => 1aa41b2.txt} (83%) rename .riot/requirements/{16ae097.txt => 1c5581b.txt} (59%) rename .riot/requirements/{1d3001d.txt => 1f06d17.txt} (60%) rename .riot/requirements/{11065bb.txt => 4de03a5.txt} (64%) rename .riot/requirements/{cbc433f.txt => 556d901.txt} (60%) rename .riot/requirements/{1fe2c8e.txt => b6e9905.txt} (64%) create mode 100644 ddtrace/appsec/_iast/_listener.py create mode 100644 ddtrace/appsec/_iast/_taint_tracking/_context.py create mode 100644 ddtrace/appsec/_iast/_taint_tracking/_debug.py create mode 100644 ddtrace/appsec/_iast/_taint_tracking/_errors.py create mode 100644 ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py diff --git a/.riot/requirements/d57247c.txt b/.riot/requirements/10f3c55.txt similarity index 59% rename from .riot/requirements/d57247c.txt rename to .riot/requirements/10f3c55.txt index ca0162432b9..34a8e65f917 100644 --- a/.riot/requirements/d57247c.txt +++ b/.riot/requirements/10f3c55.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d57247c.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/10f3c55.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==24.4.2 automat==24.8.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==43.0.0 +coverage[toml]==7.6.9 +cryptography==43.0.3 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -27,50 +28,51 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 -pytest-cov==5.0.0 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -tomli==2.0.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +tomli==2.2.1 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zipp==3.20.1 -zope-interface==7.0.3 +zeep==4.3.1 +zipp==3.21.0 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.6.0 diff --git a/.riot/requirements/2848d2c.txt b/.riot/requirements/1532dd6.txt similarity index 83% rename from .riot/requirements/2848d2c.txt rename to .riot/requirements/1532dd6.txt index fbd5a04bf84..bb2d4670425 100644 --- a/.riot/requirements/2848d2c.txt +++ b/.riot/requirements/1532dd6.txt @@ -2,22 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/2848d2c.in +# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1532dd6.in # arrow==1.2.3 asgiref==3.7.2 attrs==24.2.0 autobahn==23.1.2 automat==22.10.0 +bcrypt==4.2.1 blessed==1.20.0 cached-property==1.5.2 -certifi==2024.8.30 +certifi==2024.12.14 cffi==1.15.1 channels==3.0.5 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 constantly==15.1.0 coverage[toml]==7.2.7 -cryptography==43.0.0 +cryptography==44.0.0 daphne==3.0.2 django==3.2.25 django-configurations==2.4.2 @@ -28,11 +29,11 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 +idna==3.10 importlib-metadata==6.7.0 incremental==22.10.0 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 @@ -44,7 +45,7 @@ pyasn1==0.5.1 pyasn1-modules==0.3.0 pycparser==2.21 pylibmc==1.6.3 -pyopenssl==24.2.1 +pyopenssl==24.3.0 pytest==7.4.4 pytest-cov==4.1.0 pytest-django[testing]==3.10.0 @@ -52,13 +53,13 @@ pytest-mock==3.11.1 pytest-randomly==3.12.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.31.0 requests-file==2.1.0 requests-toolbelt==1.0.0 service-identity==21.1.0 -six==1.16.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 sqlparse==0.4.4 diff --git a/.riot/requirements/1ef9f39.txt b/.riot/requirements/1655cb3.txt similarity index 60% rename from .riot/requirements/1ef9f39.txt rename to .riot/requirements/1655cb3.txt index 024457a7bfe..f2ceb6dcf0f 100644 --- a/.riot/requirements/1ef9f39.txt +++ b/.riot/requirements/1655cb3.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ef9f39.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1655cb3.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==24.4.2 automat==24.8.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==43.0.0 +coverage[toml]==7.6.9 +cryptography==44.0.0 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -26,48 +27,48 @@ django-q==1.3.6 django-redis==4.5.0 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 +idna==3.10 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.1 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 -pytest-cov==5.0.0 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zope-interface==7.0.3 +zeep==4.3.1 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.6.0 diff --git a/.riot/requirements/d5fcd88.txt b/.riot/requirements/1aa41b2.txt similarity index 83% rename from .riot/requirements/d5fcd88.txt rename to .riot/requirements/1aa41b2.txt index acdaba91c5a..79322e51fe1 100644 --- a/.riot/requirements/d5fcd88.txt +++ b/.riot/requirements/1aa41b2.txt @@ -2,22 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/d5fcd88.in +# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1aa41b2.in # arrow==1.2.3 asgiref==3.7.2 attrs==24.2.0 autobahn==23.1.2 automat==22.10.0 +bcrypt==4.2.1 blessed==1.20.0 cached-property==1.5.2 -certifi==2024.8.30 +certifi==2024.12.14 cffi==1.15.1 channels==4.0.0 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 constantly==15.1.0 coverage[toml]==7.2.7 -cryptography==43.0.0 +cryptography==44.0.0 daphne==4.0.0 django==3.2.25 django-configurations==2.4.2 @@ -28,11 +29,11 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 +idna==3.10 importlib-metadata==6.7.0 incremental==22.10.0 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 @@ -44,7 +45,7 @@ pyasn1==0.5.1 pyasn1-modules==0.3.0 pycparser==2.21 pylibmc==1.6.3 -pyopenssl==24.2.1 +pyopenssl==24.3.0 pytest==7.4.4 pytest-cov==4.1.0 pytest-django[testing]==3.10.0 @@ -52,13 +53,13 @@ pytest-mock==3.11.1 pytest-randomly==3.12.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.31.0 requests-file==2.1.0 requests-toolbelt==1.0.0 service-identity==21.1.0 -six==1.16.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 sqlparse==0.4.4 diff --git a/.riot/requirements/16ae097.txt b/.riot/requirements/1c5581b.txt similarity index 59% rename from .riot/requirements/16ae097.txt rename to .riot/requirements/1c5581b.txt index 9b59b497e97..4886bf012bf 100644 --- a/.riot/requirements/16ae097.txt +++ b/.riot/requirements/1c5581b.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/16ae097.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c5581b.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==24.4.2 automat==24.8.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==43.0.0 +coverage[toml]==7.6.9 +cryptography==43.0.3 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -27,51 +28,50 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.1 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 -pytest-cov==5.0.0 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -tomli==2.0.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +tomli==2.2.1 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zipp==3.20.1 -zope-interface==7.0.3 +zeep==4.3.1 +zipp==3.21.0 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.6.0 diff --git a/.riot/requirements/1d3001d.txt b/.riot/requirements/1f06d17.txt similarity index 60% rename from .riot/requirements/1d3001d.txt rename to .riot/requirements/1f06d17.txt index 92cb90787bc..b22a36b05b9 100644 --- a/.riot/requirements/1d3001d.txt +++ b/.riot/requirements/1f06d17.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d3001d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f06d17.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==24.4.2 automat==24.8.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==43.0.0 +coverage[toml]==7.6.9 +cryptography==44.0.0 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -26,48 +27,48 @@ django-q==1.3.6 django-redis==4.5.0 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 +idna==3.10 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.1 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 -pytest-cov==5.0.0 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zope-interface==7.0.3 +zeep==4.3.1 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.6.0 diff --git a/.riot/requirements/11065bb.txt b/.riot/requirements/4de03a5.txt similarity index 64% rename from .riot/requirements/11065bb.txt rename to .riot/requirements/4de03a5.txt index d93c2d7bd31..5f1cc3a70e4 100644 --- a/.riot/requirements/11065bb.txt +++ b/.riot/requirements/4de03a5.txt @@ -2,24 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/11065bb.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4de03a5.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==23.1.2 automat==24.8.1 backports-zoneinfo==0.2.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 coverage[toml]==7.6.1 -cryptography==43.0.0 +cryptography==44.0.0 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -28,51 +29,50 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.1 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 +pyopenssl==24.3.0 +pytest==8.3.4 pytest-cov==5.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -tomli==2.0.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +tomli==2.2.1 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zipp==3.20.1 -zope-interface==7.0.3 +zeep==4.3.1 +zipp==3.20.2 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.3.0 diff --git a/.riot/requirements/cbc433f.txt b/.riot/requirements/556d901.txt similarity index 60% rename from .riot/requirements/cbc433f.txt rename to .riot/requirements/556d901.txt index 8dfa49d4a04..7c70525020c 100644 --- a/.riot/requirements/cbc433f.txt +++ b/.riot/requirements/556d901.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/cbc433f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/556d901.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==24.4.2 automat==24.8.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==43.0.0 +coverage[toml]==7.6.9 +cryptography==44.0.0 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -27,49 +28,49 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 +idna==3.10 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.1 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 -pytest-cov==5.0.0 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -tomli==2.0.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +tomli==2.2.1 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zope-interface==7.0.3 +zeep==4.3.1 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.6.0 diff --git a/.riot/requirements/1fe2c8e.txt b/.riot/requirements/b6e9905.txt similarity index 64% rename from .riot/requirements/1fe2c8e.txt rename to .riot/requirements/b6e9905.txt index 60fa0418337..c17865f1eae 100644 --- a/.riot/requirements/1fe2c8e.txt +++ b/.riot/requirements/b6e9905.txt @@ -2,24 +2,25 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1fe2c8e.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b6e9905.in # arrow==1.3.0 asgiref==3.8.1 -attrs==24.2.0 +attrs==24.3.0 autobahn==23.1.2 automat==24.8.1 backports-zoneinfo==0.2.1 +bcrypt==4.2.1 blessed==1.20.0 -certifi==2024.8.30 -cffi==1.17.0 -channels==4.1.0 -charset-normalizer==3.3.2 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 constantly==23.10.4 coverage[toml]==7.6.1 -cryptography==43.0.0 +cryptography==44.0.0 daphne==4.1.2 -django==4.2.15 +django==4.2.17 django-configurations==2.5.1 django-picklefield==3.2 django-pylibmc==0.6.1 @@ -28,50 +29,51 @@ django-redis==4.5.0 exceptiongroup==1.2.2 hyperlink==21.0.0 hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 +idna==3.10 +importlib-metadata==8.5.0 incremental==24.7.2 iniconfig==2.0.0 -isodate==0.6.1 +isodate==0.7.2 lxml==5.3.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 -platformdirs==4.2.2 +packaging==24.2 +platformdirs==4.3.6 pluggy==1.5.0 -psycopg2-binary==2.9.9 -pyasn1==0.6.0 -pyasn1-modules==0.4.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 pycparser==2.22 pylibmc==1.6.3 -pyopenssl==24.2.1 -pytest==8.3.2 +pyopenssl==24.3.0 +pytest==8.3.4 pytest-cov==5.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 python-dateutil==2.9.0.post0 python-memcached==1.62 -pytz==2024.1 +pytz==2024.2 redis==2.10.6 requests==2.32.3 requests-file==2.1.0 requests-toolbelt==1.0.0 -service-identity==24.1.0 -six==1.16.0 +service-identity==24.2.0 +six==1.17.0 sortedcontainers==2.4.0 spyne==2.14.0 -sqlparse==0.5.1 -tomli==2.0.1 -twisted[tls]==24.7.0 +sqlparse==0.5.3 +tomli==2.2.1 +twisted[tls]==24.11.0 txaio==23.1.1 -types-python-dateutil==2.9.0.20240821 +types-python-dateutil==2.9.0.20241206 typing-extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 wcwidth==0.2.13 -zeep==4.2.1 -zipp==3.20.1 -zope-interface==7.0.3 +zeep==4.3.1 +zipp==3.20.2 +zope-interface==7.2 # The following packages are considered to be unsafe in a requirements file: -setuptools==74.0.0 +setuptools==75.3.0 diff --git a/benchmarks/appsec_iast_aspects/scenario.py b/benchmarks/appsec_iast_aspects/scenario.py index 145b43f1633..26d9f2a37bd 100644 --- a/benchmarks/appsec_iast_aspects/scenario.py +++ b/benchmarks/appsec_iast_aspects/scenario.py @@ -11,8 +11,8 @@ from ddtrace.appsec._iast._iast_request_context import start_iast_context except ImportError: # Pre 2.15 - from ddtrace.appsec._iast._taint_tracking import create_context as start_iast_context - from ddtrace.appsec._iast._taint_tracking import reset_context as end_iast_context + from ddtrace.appsec._iast._taint_tracking._context import create_context as start_iast_context + from ddtrace.appsec._iast._taint_tracking._context import reset_context as end_iast_context set_iast_request_enabled = lambda x: None # noqa: E731 diff --git a/ddtrace/appsec/__init__.py b/ddtrace/appsec/__init__.py index bc89c0f2127..05d1a852710 100644 --- a/ddtrace/appsec/__init__.py +++ b/ddtrace/appsec/__init__.py @@ -18,7 +18,7 @@ def load_appsec(): def load_iast(): """Lazily load the iast module listeners.""" - from ddtrace.appsec._iast._iast_request_context import iast_listen + from ddtrace.appsec._iast._listener import iast_listen global _IAST_TO_BE_LOADED if _IAST_TO_BE_LOADED: diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index e3a87672e05..adb78a4447c 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -16,6 +16,12 @@ from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import EXPLOIT_PREVENTION from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from ddtrace.appsec._iast._taint_utils import taint_structure +from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.appsec._utils import add_context_log from ddtrace.appsec._utils import get_triggers from ddtrace.internal import core @@ -488,13 +494,8 @@ def _on_wrapped_view(kwargs): return_value[0] = callback_block # If IAST is enabled, taint the Flask function kwargs (path parameters) - from ddtrace.appsec._iast._utils import _is_iast_enabled if _is_iast_enabled() and kwargs: - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - if not is_iast_request_enabled(): return return_value @@ -511,11 +512,6 @@ def _on_set_request_tags(request, span, flask_config): from ddtrace.appsec._iast._utils import _is_iast_enabled if _is_iast_enabled(): - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_utils import taint_structure - _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) _set_metric_iast_instrumented_source(OriginType.COOKIE) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index e7ce12d13e9..215d8b05ee6 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -14,6 +14,7 @@ import ddtrace from ddtrace.appsec._asm_request_context import get_blocked from ddtrace.appsec._constants import WAF_ACTIONS +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.internal import core @@ -60,14 +61,12 @@ def wrapped_read_F3E51D71B4EC16EF(original_read_callable, instance, args, kwargs """ wrapper for _io.BytesIO and _io.StringIO read function """ - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - result = original_read_callable(*args, **kwargs) if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject ranges = get_tainted_ranges(instance) if len(ranges) > 0: @@ -89,8 +88,6 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs """ wrapper for open file function """ - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - if asm_config._iast_enabled and is_iast_request_enabled(): try: from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal @@ -180,8 +177,6 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, wrapper for third party requests.request function https://requests.readthedocs.io """ - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast.taint_sinks.ssrf import _iast_report_ssrf @@ -222,8 +217,6 @@ def wrapped_system_5542593D237084A7(original_command_callable, instance, args, k """ command = args[0] if args else kwargs.get("command", None) if command is not None: - from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled - if asm_config._iast_enabled and is_iast_request_enabled(): from ddtrace.appsec._iast.taint_sinks.command_injection import _iast_report_cmdi diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py index 4ba0ecc86e0..2c681e548e9 100644 --- a/ddtrace/appsec/_iast/_handlers.py +++ b/ddtrace/appsec/_iast/_handlers.py @@ -5,15 +5,22 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.appsec._iast import _is_iast_enabled +from ddtrace.appsec._iast._iast_request_context import in_iast_context from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request_body from ddtrace.appsec._iast._patch import _iast_instrument_starlette_url from ddtrace.appsec._iast._patch import _patched_dictionary from ddtrace.appsec._iast._patch import try_wrap_function_wrapper +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_utils import taint_structure from ddtrace.internal.logger import get_logger +from ._iast_request_context import is_iast_request_enabled +from ._taint_tracking._taint_objects import taint_pyobject + MessageMapContainer = None try: @@ -48,15 +55,9 @@ def _on_set_http_meta_iast( def _on_request_init(wrapped, instance, args, kwargs): - from ddtrace.appsec._iast._iast_request_context import in_iast_context - wrapped(*args, **kwargs) if _is_iast_enabled() and in_iast_context(): try: - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - instance.query_string = taint_pyobject( pyobject=instance.query_string, source_name=origin_to_str(OriginType.QUERY), @@ -75,8 +76,6 @@ def _on_request_init(wrapped, instance, args, kwargs): def _on_flask_patch(flask_version): if _is_iast_enabled(): - from ddtrace.appsec._iast._taint_tracking import OriginType - try_wrap_function_wrapper( "werkzeug.datastructures", "Headers.items", @@ -132,11 +131,7 @@ def _on_flask_patch(flask_version): def _on_wsgi_environ(wrapped, _instance, args, kwargs): - from ddtrace.appsec._iast._iast_request_context import in_iast_context - if _is_iast_enabled() and args and in_iast_context(): - from ddtrace.appsec._iast._taint_tracking import OriginType - return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) return wrapped(*args, **kwargs) @@ -145,8 +140,6 @@ def _on_wsgi_environ(wrapped, _instance, args, kwargs): def _on_django_patch(): if _is_iast_enabled(): try: - from ddtrace.appsec._iast._taint_tracking import OriginType - # we instrument those sources on _on_django_func_wrapped _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) _set_metric_iast_instrumented_source(OriginType.HEADER) @@ -169,15 +162,9 @@ def _on_django_patch(): def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): - # If IAST is enabled and we're wrapping a Django view call, taint the kwargs (view's + # If IAST is enabled, and we're wrapping a Django view call, taint the kwargs (view's # path parameters) if _is_iast_enabled() and fn_args and isinstance(fn_args[0], first_arg_expected_type): - from ddtrace.appsec._iast._iast_request_context import in_iast_context - from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - if not in_iast_context(): return @@ -243,9 +230,6 @@ def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): def _custom_protobuf_getattribute(self, name): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - ret = type(self).__saved_getattr(self, name) if isinstance(ret, (str, bytes, bytearray)): ret = taint_pyobject( @@ -295,9 +279,6 @@ def _on_grpc_response(message): def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): if _is_iast_enabled(): - from ._iast_request_context import is_iast_request_enabled - from ._taint_tracking import taint_pyobject - if not is_iast_request_enabled(): for key, value in wrapped(*args, **kwargs): yield key, value @@ -316,17 +297,11 @@ def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): value = wrapped(*args, **kwargs) - from ._iast_request_context import is_iast_request_enabled if _is_iast_enabled() and is_iast_request_enabled(): try: - from ._taint_tracking import is_pyobject_tainted - from ._taint_tracking import taint_pyobject - if not is_pyobject_tainted(value): name = str(args[0]) if len(args) else "http.request.body" - from ddtrace.appsec._iast._taint_tracking import OriginType - if origin == OriginType.HEADER and name.lower() in ["cookie", "cookies"]: origin = OriginType.COOKIE return taint_pyobject(pyobject=value, source_name=name, source_value=value, source_origin=origin) @@ -336,8 +311,6 @@ def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): def _on_iast_fastapi_patch(): - from ddtrace.appsec._iast._taint_tracking import OriginType - # Cookies sources try_wrap_function_wrapper( "starlette.requests", diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index a28c2d3ff0d..07ad4c9c238 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -8,16 +8,11 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import _is_iast_enabled from ddtrace.appsec._iast import oce -from ddtrace.appsec._iast._handlers import _on_django_func_wrapped -from ddtrace.appsec._iast._handlers import _on_django_patch -from ddtrace.appsec._iast._handlers import _on_flask_patch -from ddtrace.appsec._iast._handlers import _on_grpc_response -from ddtrace.appsec._iast._handlers import _on_request_init -from ddtrace.appsec._iast._handlers import _on_set_http_meta_iast -from ddtrace.appsec._iast._handlers import _on_wsgi_environ from ddtrace.appsec._iast._metrics import _set_metric_iast_request_tainted from ddtrace.appsec._iast._metrics import _set_span_tag_iast_executed_sink from ddtrace.appsec._iast._metrics import _set_span_tag_iast_request_tainted +from ddtrace.appsec._iast._taint_tracking._context import create_context as create_propagation_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context as reset_propagation_context from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.constants import ORIGIN_KEY from ddtrace.internal import core @@ -63,15 +58,11 @@ def in_iast_context() -> bool: def start_iast_context(): if _is_iast_enabled(): - from ._taint_tracking import create_context as create_propagation_context - create_propagation_context() core.set_item(_IAST_CONTEXT, IASTEnvironment()) def end_iast_context(span: Optional[Span] = None): - from ._taint_tracking import reset_context as reset_propagation_context - env = _get_iast_context() if env is not None and env.span is span: finalize_iast_env(env) @@ -190,22 +181,3 @@ def _iast_start_request(span=None, *args, **kwargs): set_iast_request_enabled(request_iast_enabled) except Exception: log.debug("[IAST] Error starting IAST context", exc_info=True) - - -def _on_grpc_server_response(message): - _on_grpc_response(message) - - -def iast_listen(): - core.on("grpc.client.response.message", _on_grpc_response) - core.on("grpc.server.response.message", _on_grpc_server_response) - - core.on("set_http_meta_for_asm", _on_set_http_meta_iast) - core.on("django.patch", _on_django_patch) - core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") - core.on("django.func.wrapped", _on_django_func_wrapped) - core.on("flask.patch", _on_flask_patch) - core.on("flask.request_init", _on_request_init) - - core.on("context.ended.wsgi.__call__", _iast_end_request) - core.on("context.ended.asgi.__call__", _iast_end_request) diff --git a/ddtrace/appsec/_iast/_listener.py b/ddtrace/appsec/_iast/_listener.py new file mode 100644 index 00000000000..356199a3cad --- /dev/null +++ b/ddtrace/appsec/_iast/_listener.py @@ -0,0 +1,28 @@ +from ddtrace.appsec._iast._handlers import _on_django_func_wrapped +from ddtrace.appsec._iast._handlers import _on_django_patch +from ddtrace.appsec._iast._handlers import _on_flask_patch +from ddtrace.appsec._iast._handlers import _on_grpc_response +from ddtrace.appsec._iast._handlers import _on_request_init +from ddtrace.appsec._iast._handlers import _on_set_http_meta_iast +from ddtrace.appsec._iast._handlers import _on_wsgi_environ +from ddtrace.appsec._iast._iast_request_context import _iast_end_request +from ddtrace.internal import core + + +def iast_listen(): + core.on("grpc.client.response.message", _on_grpc_response) + core.on("grpc.server.response.message", _on_grpc_server_response) + + core.on("set_http_meta_for_asm", _on_set_http_meta_iast) + core.on("django.patch", _on_django_patch) + core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") + core.on("django.func.wrapped", _on_django_func_wrapped) + core.on("flask.patch", _on_flask_patch) + core.on("flask.request_init", _on_request_init) + + core.on("context.ended.wsgi.__call__", _iast_end_request) + core.on("context.ended.asgi.__call__", _iast_end_request) + + +def _on_grpc_server_response(message): + _on_grpc_response(message) diff --git a/ddtrace/appsec/_iast/_patch.py b/ddtrace/appsec/_iast/_patch.py index 92d776c79cb..b1bf1f04989 100644 --- a/ddtrace/appsec/_iast/_patch.py +++ b/ddtrace/appsec/_iast/_patch.py @@ -5,10 +5,12 @@ from wrapt import FunctionWrapper from ddtrace.appsec._common_module_patches import wrap_object +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from ddtrace.appsec._iast._taint_utils import taint_structure from ddtrace.internal.logger import get_logger -from ._taint_utils import taint_structure - log = get_logger(__name__) @@ -48,10 +50,6 @@ def _patched_dictionary(origin_key, origin_value, original_func, instance, args, def _iast_instrument_starlette_url(wrapped, instance, args, kwargs): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - def path(self) -> str: return taint_pyobject( self.components.path, @@ -65,8 +63,6 @@ def path(self) -> str: def _iast_instrument_starlette_request(wrapped, instance, args, kwargs): - from ddtrace.appsec._iast._taint_tracking import OriginType - def receive(self): """This pattern comes from a Request._receive property, which returns a callable""" @@ -82,10 +78,6 @@ async def wrapped_property_call(): async def _iast_instrument_starlette_request_body(wrapped, instance, args, kwargs): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - result = await wrapped(*args, **kwargs) return taint_pyobject( @@ -94,9 +86,6 @@ async def _iast_instrument_starlette_request_body(wrapped, instance, args, kwarg def _iast_instrument_starlette_scope(scope): - from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject - if scope.get("path_params"): try: for k, v in scope["path_params"].items(): diff --git a/ddtrace/appsec/_iast/_patches/json_tainting.py b/ddtrace/appsec/_iast/_patches/json_tainting.py index 28cfe41e592..44df9847ba1 100644 --- a/ddtrace/appsec/_iast/_patches/json_tainting.py +++ b/ddtrace/appsec/_iast/_patches/json_tainting.py @@ -43,8 +43,8 @@ def wrapped_loads(wrapped, instance, args, kwargs): obj = wrapped(*args, **kwargs) if asm_config._iast_enabled and is_iast_request_enabled(): - from .._taint_tracking import get_tainted_ranges - from .._taint_tracking import taint_pyobject + from .._taint_tracking._taint_objects import get_tainted_ranges + from .._taint_tracking._taint_objects import taint_pyobject ranges = get_tainted_ranges(args[0]) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 839f4b3537f..3dccbd2f345 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -1,77 +1,49 @@ -from io import BytesIO -from io import StringIO -import itertools -from typing import TYPE_CHECKING # noqa:F401 -from typing import Any -from typing import Tuple - - -if TYPE_CHECKING: # pragma: no cover - from typing import Sequence # noqa:F401 - -from ddtrace.internal._unpatched import _threading as threading +from ddtrace.appsec._iast._taint_tracking._native import ops # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_format import _format_aspect # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import _convert_escaped_text_to_tainted_text + +# noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import as_formatted_evidence # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import common_replace # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import parse_params # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import set_ranges_on_splitted # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_split import _aspect_rsplit # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_split import _aspect_split # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_split import _aspect_splitlines # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathbasename # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathdirname # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathjoin # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathnormcase # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathsplit # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathsplitdrive # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathsplitext # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspects_ospath import _aspect_ospathsplitroot # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import active_map_addreses_size # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import debug_taint_map # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import initializer_size # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import num_objects_tainted # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import OriginType # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import Source # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TagMappingMode # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import are_all_text_all_ranges # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import copy_and_shift_ranges_from_strings # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import copy_ranges_from_strings # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import get_range_by_hash # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import get_ranges # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import is_tainted # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import origin_to_str # noqa: F401 + +# noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import set_ranges # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import shift_taint_range # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import shift_taint_ranges # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import str_to_origin # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import taint_range as TaintRange # noqa: F401 from ddtrace.internal.logger import get_logger -from ..._constants import IAST -from ..._constants import IAST_SPAN_TAGS -from .._iast_request_context import is_iast_request_enabled -from .._metrics import _set_iast_error_metric -from .._metrics import _set_metric_iast_executed_source -from .._metrics import increment_iast_span_metric -from .._utils import _is_iast_debug_enabled -from .._utils import _is_iast_propagation_debug_enabled -from .._utils import _is_python_version_supported - log = get_logger(__name__) -if _is_python_version_supported(): - from ._native import ops - from ._native.aspect_format import _format_aspect - from ._native.aspect_helpers import _convert_escaped_text_to_tainted_text - from ._native.aspect_helpers import as_formatted_evidence - from ._native.aspect_helpers import common_replace - from ._native.aspect_helpers import parse_params - from ._native.aspect_helpers import set_ranges_on_splitted - from ._native.aspect_split import _aspect_rsplit - from ._native.aspect_split import _aspect_split - from ._native.aspect_split import _aspect_splitlines - from ._native.aspects_ospath import _aspect_ospathbasename - from ._native.aspects_ospath import _aspect_ospathdirname - from ._native.aspects_ospath import _aspect_ospathjoin - from ._native.aspects_ospath import _aspect_ospathnormcase - from ._native.aspects_ospath import _aspect_ospathsplit - from ._native.aspects_ospath import _aspect_ospathsplitdrive - from ._native.aspects_ospath import _aspect_ospathsplitext - from ._native.aspects_ospath import _aspect_ospathsplitroot - from ._native.initializer import active_map_addreses_size - from ._native.initializer import create_context - from ._native.initializer import debug_taint_map - from ._native.initializer import initializer_size - from ._native.initializer import num_objects_tainted - from ._native.initializer import reset_context - from ._native.initializer import reset_contexts - from ._native.taint_tracking import OriginType - from ._native.taint_tracking import Source - from ._native.taint_tracking import TagMappingMode - from ._native.taint_tracking import are_all_text_all_ranges - from ._native.taint_tracking import copy_and_shift_ranges_from_strings - from ._native.taint_tracking import copy_ranges_from_strings - from ._native.taint_tracking import get_range_by_hash - from ._native.taint_tracking import get_ranges - from ._native.taint_tracking import is_notinterned_notfasttainted_unicode - from ._native.taint_tracking import is_tainted - from ._native.taint_tracking import origin_to_str - from ._native.taint_tracking import set_fast_tainted_if_notinterned_unicode - from ._native.taint_tracking import set_ranges - from ._native.taint_tracking import shift_taint_range - from ._native.taint_tracking import shift_taint_ranges - from ._native.taint_tracking import str_to_origin - from ._native.taint_tracking import taint_range as TaintRange - - new_pyobject_id = ops.new_pyobject_id - set_ranges_from_values = ops.set_ranges_from_values - __all__ = [ "OriginType", "Source", @@ -103,10 +75,9 @@ "debug_taint_map", "get_range_by_hash", "get_ranges", - "iast_taint_log_error", "initializer_size", + "is_tainted", "is_notinterned_notfasttainted_unicode", - "is_pyobject_tainted", "modulo_aspect", "new_pyobject_id", "num_objects_tainted", @@ -121,198 +92,6 @@ "shift_taint_range", "shift_taint_ranges", "str_to_origin", - "taint_pyobject", ] - - -def iast_taint_log_error(msg): - if _is_iast_debug_enabled(): - import inspect - - stack = inspect.stack() - frame_info = "\n".join("%s %s" % (frame_info.filename, frame_info.lineno) for frame_info in stack[:7]) - log.debug("[IAST] Propagation error. %s:\n%s", msg, frame_info) - _set_iast_error_metric("[IAST] Propagation error. %s" % msg) - - -def is_pyobject_tainted(pyobject: Any) -> bool: - if not is_iast_request_enabled(): - return False - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return False - - try: - return is_tainted(pyobject) - except ValueError as e: - iast_taint_log_error("Checking tainted object error: %s" % e) - return False - - -def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: - if not is_iast_request_enabled(): - return pyobject - - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return pyobject - # We need this validation in different condition if pyobject is not a text type and creates a side-effect such as - # __len__ magic method call. - pyobject_len = 0 - if isinstance(pyobject, IAST.TEXT_TYPES): - pyobject_len = len(pyobject) - if pyobject_len == 0: - return pyobject - - if isinstance(source_name, (bytes, bytearray)): - source_name = str(source_name, encoding="utf8", errors="ignore") - if isinstance(source_name, OriginType): - source_name = origin_to_str(source_name) - - if isinstance(source_value, (bytes, bytearray)): - source_value = str(source_value, encoding="utf8", errors="ignore") - if source_origin is None: - source_origin = OriginType.PARAMETER - - try: - pyobject_newid = set_ranges_from_values(pyobject, pyobject_len, source_name, source_value, source_origin) - return pyobject_newid - except ValueError as e: - log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e, exc_info=True) - return pyobject - - -def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: - try: - if source_origin is None: - source_origin = OriginType.PARAMETER - - res = _taint_pyobject_base(pyobject, source_name, source_value, source_origin) - _set_metric_iast_executed_source(source_origin) - increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SOURCE, source_origin) - return res - except ValueError as e: - log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e) - return pyobject - - -def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: - if not is_iast_request_enabled(): - return False - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return False - try: - set_ranges(pyobject, ranges) - return True - except ValueError as e: - iast_taint_log_error("Tainting object with ranges error (pyobject type %s): %s" % (type(pyobject), e)) - return False - - -def get_tainted_ranges(pyobject: Any) -> Tuple: - if not is_iast_request_enabled(): - return tuple() - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return tuple() - try: - return get_ranges(pyobject) - except ValueError as e: - iast_taint_log_error("Get ranges error (pyobject type %s): %s" % (type(pyobject), e)) - return tuple() - - -if _is_iast_propagation_debug_enabled(): - TAINTED_FRAMES = [] - - def trace_calls_and_returns(frame, event, arg): - co = frame.f_code - func_name = co.co_name - if func_name == "write": - # Ignore write() calls from print statements - return - if func_name in ("is_pyobject_tainted", "__repr__"): - return - line_no = frame.f_lineno - filename = co.co_filename - if "ddtrace" in filename: - return - if event == "call": - f_locals = frame.f_locals - try: - if any([is_pyobject_tainted(f_locals[arg]) for arg in f_locals]): - TAINTED_FRAMES.append(frame) - log.debug("Call to %s on line %s of %s, args: %s", func_name, line_no, filename, frame.f_locals) - log.debug("Tainted arguments:") - for arg in f_locals: - if is_pyobject_tainted(f_locals[arg]): - log.debug("\t%s: %s", arg, f_locals[arg]) - log.debug("-----") - return trace_calls_and_returns - except AttributeError: - pass - elif event == "return": - if frame in TAINTED_FRAMES: - TAINTED_FRAMES.remove(frame) - log.debug("Return from %s on line %d of %s, return value: %s", func_name, line_no, filename, arg) - if isinstance(arg, (str, bytes, bytearray, BytesIO, StringIO, list, tuple, dict)): - if ( - (isinstance(arg, (str, bytes, bytearray, BytesIO, StringIO)) and is_pyobject_tainted(arg)) - or (isinstance(arg, (list, tuple)) and any([is_pyobject_tainted(x) for x in arg])) - or (isinstance(arg, dict) and any([is_pyobject_tainted(x) for x in arg.values()])) - ): - log.debug("Return value is tainted") - else: - log.debug("Return value is NOT tainted") - log.debug("-----") - return - - threading.settrace(trace_calls_and_returns) - - -def copy_ranges_to_string(pyobject, ranges): - # type: (str, Sequence[TaintRange]) -> str - # NB this function uses comment-based type annotation because TaintRange is conditionally imported - if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] - return pyobject - - for r in ranges: - _is_string_in_source_value = False - if r.source.value: - if isinstance(pyobject, (bytes, bytearray)): - pyobject_str = str(pyobject, encoding="utf8", errors="ignore") - else: - pyobject_str = pyobject - _is_string_in_source_value = pyobject_str in r.source.value - - if _is_string_in_source_value: - pyobject = _taint_pyobject_base( - pyobject=pyobject, - source_name=r.source.name, - source_value=r.source.value, - source_origin=r.source.origin, - ) - break - else: - # no total match found, maybe partial match, just take the first one - pyobject = _taint_pyobject_base( - pyobject=pyobject, - source_name=ranges[0].source.name, - source_value=ranges[0].source.value, - source_origin=ranges[0].source.origin, - ) - return pyobject - - -# Given a list of ranges, try to match them with the iterable and return a new iterable with a new range applied that -# matched the original one Source. If no range matches, take the Source from the first one. -def copy_ranges_to_iterable_with_strings(iterable, ranges): - # type: (Sequence[str], Sequence[TaintRange]) -> Sequence[str] - # NB this function uses comment-based type annotation because TaintRange is conditionally imported - iterable_type = type(iterable) - - new_result = [] - # do this so it doesn't consume a potential generator - items, items_backup = itertools.tee(iterable) - for i in items_backup: - i = copy_ranges_to_string(i, ranges) - new_result.append(i) - - return iterable_type(new_result) # type: ignore[call-arg] +new_pyobject_id = ops.new_pyobject_id +set_ranges_from_values = ops.set_ranges_from_values diff --git a/ddtrace/appsec/_iast/_taint_tracking/_context.py b/ddtrace/appsec/_iast/_taint_tracking/_context.py new file mode 100644 index 00000000000..160d229faec --- /dev/null +++ b/ddtrace/appsec/_iast/_taint_tracking/_context.py @@ -0,0 +1,10 @@ +from ddtrace.appsec._iast._taint_tracking._native.initializer import create_context # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import reset_context # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.initializer import reset_contexts # noqa: F401 + + +__all__ = [ + "create_context", + "reset_context", + "reset_contexts", +] diff --git a/ddtrace/appsec/_iast/_taint_tracking/_debug.py b/ddtrace/appsec/_iast/_taint_tracking/_debug.py new file mode 100644 index 00000000000..6b7e6ec4d3d --- /dev/null +++ b/ddtrace/appsec/_iast/_taint_tracking/_debug.py @@ -0,0 +1,57 @@ +from io import BytesIO +from io import StringIO + +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._utils import _is_iast_propagation_debug_enabled +from ddtrace.internal._unpatched import _threading as threading +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + +if _is_iast_propagation_debug_enabled(): + TAINTED_FRAMES = [] + + def trace_calls_and_returns(frame, event, arg): + co = frame.f_code + func_name = co.co_name + if func_name == "write": + # Ignore write() calls from print statements + return + if func_name in ("is_pyobject_tainted", "__repr__"): + return + line_no = frame.f_lineno + filename = co.co_filename + if "ddtrace" in filename: + return + if event == "call": + f_locals = frame.f_locals + try: + if any([is_pyobject_tainted(f_locals[arg]) for arg in f_locals]): + TAINTED_FRAMES.append(frame) + log.debug("Call to %s on line %s of %s, args: %s", func_name, line_no, filename, frame.f_locals) + log.debug("Tainted arguments:") + for arg in f_locals: + if is_pyobject_tainted(f_locals[arg]): + log.debug("\t%s: %s", arg, f_locals[arg]) + log.debug("-----") + return trace_calls_and_returns + except AttributeError: + pass + elif event == "return": + if frame in TAINTED_FRAMES: + TAINTED_FRAMES.remove(frame) + log.debug("Return from %s on line %d of %s, return value: %s", func_name, line_no, filename, arg) + if isinstance(arg, (str, bytes, bytearray, BytesIO, StringIO, list, tuple, dict)): + if ( + (isinstance(arg, (str, bytes, bytearray, BytesIO, StringIO)) and is_pyobject_tainted(arg)) + or (isinstance(arg, (list, tuple)) and any([is_pyobject_tainted(x) for x in arg])) + or (isinstance(arg, dict) and any([is_pyobject_tainted(x) for x in arg.values()])) + ): + log.debug("Return value is tainted") + else: + log.debug("Return value is NOT tainted") + log.debug("-----") + return + + threading.settrace(trace_calls_and_returns) diff --git a/ddtrace/appsec/_iast/_taint_tracking/_errors.py b/ddtrace/appsec/_iast/_taint_tracking/_errors.py new file mode 100644 index 00000000000..0d7c2fb856b --- /dev/null +++ b/ddtrace/appsec/_iast/_taint_tracking/_errors.py @@ -0,0 +1,16 @@ +import inspect + +from ddtrace.appsec._iast._metrics import _set_iast_error_metric +from ddtrace.appsec._iast._utils import _is_iast_debug_enabled +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def iast_taint_log_error(msg): + if _is_iast_debug_enabled(): + stack = inspect.stack() + frame_info = "\n".join("%s %s" % (frame_info.filename, frame_info.lineno) for frame_info in stack[:7]) + log.debug("[IAST] Propagation error. %s:\n%s", msg, frame_info) + _set_iast_error_metric("[IAST] Propagation error. %s" % msg) diff --git a/ddtrace/appsec/_iast/_taint_tracking/_native.cpp b/ddtrace/appsec/_iast/_taint_tracking/_native.cpp index 170c12d8429..e0605a853b5 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/_native.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/_native.cpp @@ -64,19 +64,6 @@ static struct PyModuleDef ops = { PyModuleDef_HEAD_INIT, */ PYBIND11_MODULE(_native, m) { - const char* env_iast_enabled = std::getenv("DD_IAST_ENABLED"); - if (env_iast_enabled == nullptr) { - py::module::import("logging").attr("warning")("IAST not enabled but native module is being loaded"); - } else { - std::string iast_enabled = std::string(env_iast_enabled); - std::transform(iast_enabled.begin(), iast_enabled.end(), iast_enabled.begin(), [](unsigned char c) { - return std::tolower(c); - }); - if (iast_enabled != "true" && iast_enabled != "1") { - py::module::import("logging").attr("warning")("IAST not enabled but native module is being loaded"); - } - } - initializer = make_unique(); // Create a atexit callback to cleanup the Initializer before the interpreter finishes diff --git a/ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py b/ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py new file mode 100644 index 00000000000..660e8b8e69b --- /dev/null +++ b/ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py @@ -0,0 +1,154 @@ +import itertools +from typing import Any +from typing import Sequence +from typing import Tuple + +from ddtrace.appsec._constants import IAST +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_source +from ddtrace.appsec._iast._metrics import increment_iast_span_metric +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import TaintRange +from ddtrace.appsec._iast._taint_tracking import get_ranges +from ddtrace.appsec._iast._taint_tracking import is_tainted +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking import set_ranges +from ddtrace.appsec._iast._taint_tracking import set_ranges_from_values +from ddtrace.appsec._iast._taint_tracking._errors import iast_taint_log_error +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def is_pyobject_tainted(pyobject: Any) -> bool: + if not is_iast_request_enabled(): + return False + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return False + + try: + return is_tainted(pyobject) + except ValueError as e: + iast_taint_log_error("Checking tainted object error: %s" % e) + return False + + +def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: + if not is_iast_request_enabled(): + return pyobject + + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return pyobject + # We need this validation in different condition if pyobject is not a text type and creates a side-effect such as + # __len__ magic method call. + pyobject_len = 0 + if isinstance(pyobject, IAST.TEXT_TYPES): + pyobject_len = len(pyobject) + if pyobject_len == 0: + return pyobject + + if isinstance(source_name, (bytes, bytearray)): + source_name = str(source_name, encoding="utf8", errors="ignore") + if isinstance(source_name, OriginType): + source_name = origin_to_str(source_name) + + if isinstance(source_value, (bytes, bytearray)): + source_value = str(source_value, encoding="utf8", errors="ignore") + if source_origin is None: + source_origin = OriginType.PARAMETER + + try: + pyobject_newid = set_ranges_from_values(pyobject, pyobject_len, source_name, source_value, source_origin) + return pyobject_newid + except ValueError as e: + log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e, exc_info=True) + return pyobject + + +def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: + if not is_iast_request_enabled(): + return False + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return False + try: + set_ranges(pyobject, ranges) + return True + except ValueError as e: + iast_taint_log_error("Tainting object with ranges error (pyobject type %s): %s" % (type(pyobject), e)) + return False + + +def get_tainted_ranges(pyobject: Any) -> Tuple: + if not is_iast_request_enabled(): + return tuple() + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return tuple() + try: + return get_ranges(pyobject) + except ValueError as e: + iast_taint_log_error("Get ranges error (pyobject type %s): %s" % (type(pyobject), e)) + return tuple() + + +def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: + try: + if source_origin is None: + source_origin = OriginType.PARAMETER + + res = _taint_pyobject_base(pyobject, source_name, source_value, source_origin) + _set_metric_iast_executed_source(source_origin) + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SOURCE, source_origin) + return res + except ValueError as e: + log.debug("Tainting object error (pyobject type %s): %s", type(pyobject), e) + return pyobject + + +def copy_ranges_to_string(pyobject: str, ranges: Sequence[TaintRange]) -> str: + # NB this function uses comment-based type annotation because TaintRange is conditionally imported + if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc] + return pyobject + + for r in ranges: + _is_string_in_source_value = False + if r.source.value: + if isinstance(pyobject, (bytes, bytearray)): + pyobject_str = str(pyobject, encoding="utf8", errors="ignore") + else: + pyobject_str = pyobject + _is_string_in_source_value = pyobject_str in r.source.value + + if _is_string_in_source_value: + pyobject = _taint_pyobject_base( + pyobject=pyobject, + source_name=r.source.name, + source_value=r.source.value, + source_origin=r.source.origin, + ) + break + else: + # no total match found, maybe partial match, just take the first one + pyobject = _taint_pyobject_base( + pyobject=pyobject, + source_name=ranges[0].source.name, + source_value=ranges[0].source.value, + source_origin=ranges[0].source.origin, + ) + return pyobject + + +def copy_ranges_to_iterable_with_strings(iterable, ranges): + # type: (Sequence[str], Sequence[TaintRange]) -> Sequence[str] + # NB this function uses comment-based type annotation because TaintRange is conditionally imported + iterable_type = type(iterable) + + new_result = [] + # do this so it doesn't consume a potential generator + items, items_backup = itertools.tee(iterable) + for i in items_backup: + i = copy_ranges_to_string(i, ranges) + new_result.append(i) + + return iterable_type(new_result) # type: ignore[call-arg] diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index d70dc76449c..925cd4a5b9d 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -19,39 +19,38 @@ import _io from ddtrace.appsec._constants import IAST - -from .._taint_tracking import TagMappingMode -from .._taint_tracking import TaintRange -from .._taint_tracking import _aspect_ospathbasename -from .._taint_tracking import _aspect_ospathdirname -from .._taint_tracking import _aspect_ospathjoin -from .._taint_tracking import _aspect_ospathnormcase -from .._taint_tracking import _aspect_ospathsplit -from .._taint_tracking import _aspect_ospathsplitdrive -from .._taint_tracking import _aspect_ospathsplitext -from .._taint_tracking import _aspect_ospathsplitroot -from .._taint_tracking import _aspect_rsplit -from .._taint_tracking import _aspect_split -from .._taint_tracking import _aspect_splitlines -from .._taint_tracking import _convert_escaped_text_to_tainted_text -from .._taint_tracking import _format_aspect -from .._taint_tracking import are_all_text_all_ranges -from .._taint_tracking import as_formatted_evidence -from .._taint_tracking import common_replace -from .._taint_tracking import copy_and_shift_ranges_from_strings -from .._taint_tracking import copy_ranges_from_strings -from .._taint_tracking import copy_ranges_to_iterable_with_strings -from .._taint_tracking import copy_ranges_to_string -from .._taint_tracking import get_ranges -from .._taint_tracking import get_tainted_ranges -from .._taint_tracking import iast_taint_log_error -from .._taint_tracking import is_pyobject_tainted -from .._taint_tracking import new_pyobject_id -from .._taint_tracking import parse_params -from .._taint_tracking import set_ranges -from .._taint_tracking import shift_taint_range -from .._taint_tracking import taint_pyobject_with_ranges -from .._taint_tracking._native import aspects # noqa: F401 +from ddtrace.appsec._iast._taint_tracking import TagMappingMode +from ddtrace.appsec._iast._taint_tracking import TaintRange +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathbasename +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathdirname +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathjoin +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathnormcase +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplit +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitdrive +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitext +from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitroot +from ddtrace.appsec._iast._taint_tracking import _aspect_rsplit +from ddtrace.appsec._iast._taint_tracking import _aspect_split +from ddtrace.appsec._iast._taint_tracking import _aspect_splitlines +from ddtrace.appsec._iast._taint_tracking import _convert_escaped_text_to_tainted_text +from ddtrace.appsec._iast._taint_tracking import _format_aspect +from ddtrace.appsec._iast._taint_tracking import are_all_text_all_ranges +from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence +from ddtrace.appsec._iast._taint_tracking import common_replace +from ddtrace.appsec._iast._taint_tracking import copy_and_shift_ranges_from_strings +from ddtrace.appsec._iast._taint_tracking import copy_ranges_from_strings +from ddtrace.appsec._iast._taint_tracking import get_ranges +from ddtrace.appsec._iast._taint_tracking import new_pyobject_id +from ddtrace.appsec._iast._taint_tracking import parse_params +from ddtrace.appsec._iast._taint_tracking import set_ranges +from ddtrace.appsec._iast._taint_tracking import shift_taint_range +from ddtrace.appsec._iast._taint_tracking._errors import iast_taint_log_error +from ddtrace.appsec._iast._taint_tracking._native import aspects # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._taint_objects import copy_ranges_to_iterable_with_strings +from ddtrace.appsec._iast._taint_tracking._taint_objects import copy_ranges_to_string +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject_with_ranges TEXT_TYPES = Union[str, bytes, bytearray] diff --git a/ddtrace/appsec/_iast/_taint_utils.py b/ddtrace/appsec/_iast/_taint_utils.py index 8b5e1b97caa..524e8279d2b 100644 --- a/ddtrace/appsec/_iast/_taint_utils.py +++ b/ddtrace/appsec/_iast/_taint_utils.py @@ -5,6 +5,8 @@ from typing import Optional from typing import Union +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import DBAPI_INTEGRATIONS from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config @@ -87,9 +89,6 @@ def taint_structure(main_obj, source_key, source_value, override_pyobject_tainte use a queue like mechanism to avoid recursion Best effort: mutate mutable structures and rebuild immutable ones if possible """ - from ._taint_tracking import is_pyobject_tainted - from ._taint_tracking import taint_pyobject - if not main_obj: return main_obj @@ -164,9 +163,6 @@ def __init__(self, original_list, origins=(0, 0), override_pyobject_tainted=Fals def _taint(self, value): if value: if isinstance(value, (str, bytes, bytearray)): - from ._taint_tracking import is_pyobject_tainted - from ._taint_tracking import taint_pyobject - if not is_pyobject_tainted(value) or self._override_pyobject_tainted: try: # TODO: migrate this part to shift ranges instead of creating a new one @@ -348,9 +344,6 @@ def _taint(self, value, key, origin=None): origin = self._origin_value if value: if isinstance(value, (str, bytes, bytearray)): - from ._taint_tracking import is_pyobject_tainted - from ._taint_tracking import taint_pyobject - if not is_pyobject_tainted(value) or self._override_pyobject_tainted: try: # TODO: migrate this part to shift ranges instead of creating a new one @@ -529,8 +522,6 @@ def supported_dbapi_integration(integration_name): def check_tainted_dbapi_args(args, kwargs, tracer, integration_name, method): if supported_dbapi_integration(integration_name) and method.__name__ == "execute": - from ._taint_tracking import is_pyobject_tainted - return len(args) and args[0] and is_pyobject_tainted(args[0]) return False diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index c1ae2d82be4..fda05a8b8e5 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -8,8 +8,8 @@ @lru_cache(maxsize=1) def _is_python_version_supported() -> bool: - # IAST supports Python versions 3.6 to 3.12 - return (3, 6, 0) <= sys.version_info < (3, 13, 0) + # IAST supports Python versions 3.6 to 3.13 + return (3, 6, 0) <= sys.version_info < (3, 14, 0) def _is_iast_enabled(): diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index 62cc2ee8d65..ffdd786fc28 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -218,7 +218,7 @@ def taint_ranges_as_evidence_info(pyobject: Any) -> Tuple[List[Source], List[Dic Returns: - Tuple[Set[Source], List[Dict]]: Set of Source objects and list of tainted ranges as dictionaries. """ - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges sources = list() tainted_ranges = get_tainted_ranges(pyobject) diff --git a/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/ddtrace/appsec/_iast/taint_sinks/command_injection.py index 0cfd48a5816..ee22b294bfc 100644 --- a/ddtrace/appsec/_iast/taint_sinks/command_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -3,17 +3,19 @@ from typing import List from typing import Union +from ddtrace.appsec._common_module_patches import try_unwrap +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast._metrics import increment_iast_span_metric +from ddtrace.appsec._iast._patch import try_wrap_function_wrapper +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config -from ..._common_module_patches import try_unwrap -from ..._constants import IAST_SPAN_TAGS -from .. import oce -from .._iast_request_context import is_iast_request_enabled -from .._metrics import _set_metric_iast_instrumented_sink -from .._metrics import increment_iast_span_metric -from .._patch import try_wrap_function_wrapper -from ..constants import VULN_CMDI from ._base import VulnerabilityBase @@ -75,13 +77,11 @@ class CommandInjection(VulnerabilityBase): def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None: report_cmdi = "" - from .._metrics import _set_metric_iast_executed_sink increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CommandInjection.vulnerability_type) _set_metric_iast_executed_sink(CommandInjection.vulnerability_type) if is_iast_request_enabled() and CommandInjection.has_quota(): - from .._taint_tracking import is_pyobject_tainted from .._taint_tracking.aspects import join_aspect if isinstance(shell_args, (list, tuple)): diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 4d56986c2d0..730e9f05490 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -2,20 +2,22 @@ from wrapt.importer import when_imported +from ddtrace.appsec._common_module_patches import try_unwrap +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast._metrics import increment_iast_span_metric +from ddtrace.appsec._iast._patch import set_and_check_module_is_patched +from ddtrace.appsec._iast._patch import set_module_unpatched +from ddtrace.appsec._iast._patch import try_wrap_function_wrapper +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast.constants import HEADER_NAME_VALUE_SEPARATOR +from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config -from ..._common_module_patches import try_unwrap -from ..._constants import IAST_SPAN_TAGS -from .. import oce -from .._iast_request_context import is_iast_request_enabled -from .._metrics import _set_metric_iast_instrumented_sink -from .._metrics import increment_iast_span_metric -from .._patch import set_and_check_module_is_patched -from .._patch import set_module_unpatched -from .._patch import try_wrap_function_wrapper -from ..constants import HEADER_NAME_VALUE_SEPARATOR -from ..constants import VULN_HEADER_INJECTION from ._base import VulnerabilityBase @@ -97,9 +99,7 @@ class HeaderInjection(VulnerabilityBase): def _iast_report_header_injection(headers_args) -> None: - from .._metrics import _set_metric_iast_executed_sink - from .._taint_tracking import is_pyobject_tainted - from .._taint_tracking.aspects import add_aspect + from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect header_name, header_value = headers_args for header_to_exclude in HEADER_INJECTION_EXCLUSIONS: diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index f4cb00fc433..3e9c05c451c 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -7,7 +7,7 @@ from .._iast_request_context import is_iast_request_enabled from .._metrics import _set_metric_iast_executed_sink from .._metrics import increment_iast_span_metric -from .._taint_tracking import iast_taint_log_error +from .._taint_tracking._errors import iast_taint_log_error from ..constants import VULN_INSECURE_COOKIE from ..constants import VULN_NO_HTTPONLY_COOKIE from ..constants import VULN_NO_SAMESITE_COOKIE diff --git a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py index 1fd9cff8956..42c1045c7ce 100644 --- a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py +++ b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py @@ -1,12 +1,14 @@ from typing import Any +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink +from ddtrace.appsec._iast._metrics import increment_iast_span_metric +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.internal.logger import get_logger -from ..._constants import IAST_SPAN_TAGS -from .. import oce -from .._iast_request_context import is_iast_request_enabled -from .._metrics import increment_iast_span_metric -from ..constants import VULN_PATH_TRAVERSAL from ._base import VulnerabilityBase @@ -21,9 +23,6 @@ class PathTraversal(VulnerabilityBase): def check_and_report_path_traversal(*args: Any, **kwargs: Any) -> None: if is_iast_request_enabled() and PathTraversal.has_quota(): try: - from .._metrics import _set_metric_iast_executed_sink - from .._taint_tracking import is_pyobject_tainted - increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, PathTraversal.vulnerability_type) _set_metric_iast_executed_sink(PathTraversal.vulnerability_type) filename_arg = args[0] if args else kwargs.get("file", None) diff --git a/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/ddtrace/appsec/_iast/taint_sinks/ssrf.py index 7233aa54cec..5090e73bc76 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ssrf.py +++ b/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -1,15 +1,17 @@ from typing import Callable +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled +from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink +from ddtrace.appsec._iast._metrics import increment_iast_span_metric +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.importlib import func_name -from ..._constants import IAST_SPAN_TAGS -from .. import oce -from .._iast_request_context import is_iast_request_enabled -from .._metrics import increment_iast_span_metric -from ..constants import VULN_SSRF from ._base import VulnerabilityBase @@ -46,14 +48,10 @@ def _iast_report_ssrf(func: Callable, *args, **kwargs): return if report_ssrf: - from .._metrics import _set_metric_iast_executed_sink - _set_metric_iast_executed_sink(SSRF.vulnerability_type) increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SSRF.vulnerability_type) if is_iast_request_enabled() and SSRF.has_quota(): try: - from .._taint_tracking import is_pyobject_tainted - if is_pyobject_tainted(report_ssrf): SSRF.report(evidence_value=report_ssrf) except Exception: diff --git a/ddtrace/contrib/internal/langchain/patch.py b/ddtrace/contrib/internal/langchain/patch.py index fa2332d70f2..b7513539da7 100644 --- a/ddtrace/contrib/internal/langchain/patch.py +++ b/ddtrace/contrib/internal/langchain/patch.py @@ -1406,8 +1406,8 @@ def unpatch(): def taint_outputs(instance, inputs, outputs): from ddtrace.appsec._iast._metrics import _set_iast_error_metric - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject try: ranges = None @@ -1429,8 +1429,8 @@ def taint_outputs(instance, inputs, outputs): def taint_parser_output(func, instance, args, kwargs): from ddtrace.appsec._iast._metrics import _set_iast_error_metric - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject result = func(*args, **kwargs) try: diff --git a/riotfile.py b/riotfile.py index e7a078a5425..c674a97ac4b 100644 --- a/riotfile.py +++ b/riotfile.py @@ -806,6 +806,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "django-q": latest, "spyne": latest, "zeep": latest, + "bcrypt": "==4.2.1", }, env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", diff --git a/scripts/iast/leak_functions.py b/scripts/iast/leak_functions.py index 55fdcb0bbaa..ebac2253858 100644 --- a/scripts/iast/leak_functions.py +++ b/scripts/iast/leak_functions.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled from ddtrace.appsec._iast._iast_request_context import start_iast_context from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from tests.utils import override_env diff --git a/scripts/iast/mod_leak_functions.py b/scripts/iast/mod_leak_functions.py index 40e7e5a99b7..bf96d93c497 100644 --- a/scripts/iast/mod_leak_functions.py +++ b/scripts/iast/mod_leak_functions.py @@ -13,8 +13,8 @@ import requests from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject v = SchemaValidator( diff --git a/scripts/iast/test_references.py b/scripts/iast/test_references.py index d4cdd2fcc16..8fce6e0cdd7 100644 --- a/scripts/iast/test_references.py +++ b/scripts/iast/test_references.py @@ -4,9 +4,9 @@ from mod_leak_functions import test_doit -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import reset_context +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted async def test_main(): diff --git a/tests/appsec/app.py b/tests/appsec/app.py index 103341c752a..eb5beb666cf 100644 --- a/tests/appsec/app.py +++ b/tests/appsec/app.py @@ -239,7 +239,7 @@ def iast_ast_patching_io_bytes_io_untainted(): changed = BytesIO(bytes_filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not is_pyobject_tainted(changed): resp = Response("OK") @@ -270,7 +270,7 @@ def iast_ast_patching_io_string_io_untainted(): changed = StringIO(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not is_pyobject_tainted(changed): resp = Response("OK") @@ -302,7 +302,7 @@ def iast_ast_patching_io_bytes_io_read_untainted(): changed = BytesIO(bytes_filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not is_pyobject_tainted(changed.read(4)): resp = Response("OK") @@ -333,7 +333,7 @@ def iast_ast_patching_io_string_io_read_untainted(): changed = StringIO(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not is_pyobject_tainted(changed.read(4)): resp = Response("OK") @@ -365,7 +365,7 @@ def iast_ast_patching_io_bytes_io(): changed = BytesIO(bytes_filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("OK") @@ -396,7 +396,7 @@ def iast_ast_patching_io_string_io(): changed = StringIO(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("OK") @@ -428,7 +428,7 @@ def iast_ast_patching_io_bytes_io_read(): changed = BytesIO(bytes_filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed.read(4)): resp = Response("OK") @@ -459,7 +459,7 @@ def iast_ast_patching_io_string_io_read(): changed = StringIO(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed.read(4)): resp = Response("OK") @@ -479,7 +479,7 @@ def iast_ast_patching_re_sub(): changed = pattern.sub(" ", filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("OK") @@ -501,7 +501,7 @@ def iast_ast_patching_non_re_sub(): changed = pattern.sub(" ", filename) resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("Fail") @@ -521,7 +521,7 @@ def iast_ast_patching_re_subn(): changed, number = pattern.subn(" ", filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("OK") @@ -543,7 +543,7 @@ def iast_ast_patching_non_re_subn(): changed, number = pattern.subn(" ", filename) resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if is_pyobject_tainted(changed): resp = Response("Fail") @@ -563,7 +563,7 @@ def iast_ast_patching_re_split(): result = pattern.split(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -585,7 +585,7 @@ def iast_ast_patching_non_re_split(): result = pattern.split(filename) resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if any(map(is_pyobject_tainted, result)): resp = Response("Fail") @@ -605,7 +605,7 @@ def iast_ast_patching_re_findall(): result = pattern.findall(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -627,7 +627,7 @@ def iast_ast_patching_non_re_findall(): result = pattern.findall(filename) resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if any(map(is_pyobject_tainted, result)): resp = Response("Fail") @@ -647,7 +647,7 @@ def iast_ast_patching_re_finditer(): result = pattern.finditer(filename) resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -669,7 +669,7 @@ def iast_ast_patching_non_re_finditer(): result = pattern.finditer(filename) resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if any(map(is_pyobject_tainted, result)): resp = Response("Fail") @@ -697,7 +697,7 @@ def iast_ast_patching_re_groups(): result = [] resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if result and all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -727,7 +727,7 @@ def iast_ast_patching_non_re_groups(): result = [] resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not result or any(map(is_pyobject_tainted, result)): resp = Response("Fail") @@ -755,7 +755,7 @@ def iast_ast_patching_re_string(): result = None resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if result and is_pyobject_tainted(result): resp = Response("OK") @@ -785,7 +785,7 @@ def iast_ast_patching_non_re_string(): result = None resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not result or is_pyobject_tainted(result): resp = Response("Fail") @@ -813,7 +813,7 @@ def iast_ast_patching_re_fullmatch(): result = [] resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if result and all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -843,7 +843,7 @@ def iast_ast_patching_non_re_fullmatch(): result = [] resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not result or any(map(is_pyobject_tainted, result)): resp = Response("Fail") @@ -871,7 +871,7 @@ def iast_ast_patching_re_expand(): result = None resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if result and is_pyobject_tainted(result): resp = Response("OK") @@ -901,7 +901,7 @@ def iast_ast_patching_non_re_expand(): result = None resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not result or is_pyobject_tainted(result): resp = Response("Fail") @@ -929,7 +929,7 @@ def iast_ast_patching_re_search(): result = [] resp = Response("Fail") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if result and all(map(is_pyobject_tainted, result)): resp = Response("OK") @@ -959,7 +959,7 @@ def iast_ast_patching_non_re_search(): result = [] resp = Response("OK") try: - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted if not result or any(map(is_pyobject_tainted, result)): resp = Response("Fail") diff --git a/tests/appsec/iast/aspects/aspect_utils.py b/tests/appsec/iast/aspects/aspect_utils.py index e62625e256b..0467915e672 100644 --- a/tests/appsec/iast/aspects/aspect_utils.py +++ b/tests/appsec/iast/aspects/aspect_utils.py @@ -11,7 +11,7 @@ from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence from ddtrace.appsec._iast._taint_tracking import set_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject_with_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject_with_ranges from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index f9f86a4413c..a2e4558198f 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -5,12 +5,12 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TaintRange_ +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from tests.appsec.iast.conftest import _end_iast_context_and_oce diff --git a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py index 19a6a97dae7..854cbea2032 100644 --- a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py @@ -4,8 +4,8 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_add_inplace_aspect.py b/tests/appsec/iast/aspects/test_add_inplace_aspect.py index b6d2b22dce8..babe9bae651 100644 --- a/tests/appsec/iast/aspects/test_add_inplace_aspect.py +++ b/tests/appsec/iast/aspects/test_add_inplace_aspect.py @@ -5,10 +5,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TaintRange_ +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects diff --git a/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py b/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py index 1d59ba41dbc..4fc96486be0 100644 --- a/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_add_inplace_aspect_fixtures.py @@ -4,8 +4,8 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_asyncio.py b/tests/appsec/iast/aspects/test_asyncio.py index e01627819b3..1bd8bd7b45a 100644 --- a/tests/appsec/iast/aspects/test_asyncio.py +++ b/tests/appsec/iast/aspects/test_asyncio.py @@ -5,9 +5,9 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py index e746800cd3f..41047e47203 100644 --- a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py +++ b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py @@ -6,10 +6,10 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_common_replace_aspects.py b/tests/appsec/iast/aspects/test_common_replace_aspects.py index f858c065aa1..7054b34e83b 100644 --- a/tests/appsec/iast/aspects/test_common_replace_aspects.py +++ b/tests/appsec/iast/aspects/test_common_replace_aspects.py @@ -3,8 +3,8 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_encode_decode_aspect.py b/tests/appsec/iast/aspects/test_encode_decode_aspect.py index 6e994f6e9fa..a5da649c1fe 100644 --- a/tests/appsec/iast/aspects/test_encode_decode_aspect.py +++ b/tests/appsec/iast/aspects/test_encode_decode_aspect.py @@ -3,8 +3,8 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py index a35a424d67b..b401589af92 100644 --- a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py @@ -9,10 +9,10 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.aspect_utils import create_taint_range_with_format from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 0542cd636c8..8ab46d9bf45 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -4,10 +4,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_io_aspects.py b/tests/appsec/iast/aspects/test_io_aspects.py index ea74825895d..177e0742674 100644 --- a/tests/appsec/iast/aspects/test_io_aspects.py +++ b/tests/appsec/iast/aspects/test_io_aspects.py @@ -3,9 +3,9 @@ from ddtrace.appsec._common_module_patches import patch_common_modules from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast._taint_tracking.aspects import bytesio_aspect from ddtrace.appsec._iast._taint_tracking.aspects import stringio_aspect diff --git a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py index 8692485f295..d4c896786f6 100644 --- a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py @@ -5,10 +5,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py b/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py index 80ca12a2db8..175b5bf9439 100644 --- a/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_modulo_aspect_fixtures.py @@ -11,8 +11,8 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_ospath_aspects.py b/tests/appsec/iast/aspects/test_ospath_aspects.py index 976327cdd2c..9e1b5eee93f 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects.py @@ -18,8 +18,8 @@ from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplitdrive_aspect if sys.version_info >= (3, 12): from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplitroot_aspect -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject def test_ospathjoin_first_arg_nottainted_noslash(): diff --git a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py index 68ac2cba76e..7cd2069dbfd 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py @@ -8,10 +8,10 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_other_patching.py b/tests/appsec/iast/aspects/test_other_patching.py index d392fdb7c4b..a7b620a1792 100644 --- a/tests/appsec/iast/aspects/test_other_patching.py +++ b/tests/appsec/iast/aspects/test_other_patching.py @@ -5,8 +5,8 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/aspects/test_re_aspects.py b/tests/appsec/iast/aspects/test_re_aspects.py index b5069948a89..55d2ea81f03 100644 --- a/tests/appsec/iast/aspects/test_re_aspects.py +++ b/tests/appsec/iast/aspects/test_re_aspects.py @@ -6,9 +6,9 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast._taint_tracking.aspects import index_aspect from ddtrace.appsec._iast._taint_tracking.aspects import re_expand_aspect diff --git a/tests/appsec/iast/aspects/test_replace_aspect.py b/tests/appsec/iast/aspects/test_replace_aspect.py index b30fa7cdede..b929817e3eb 100644 --- a/tests/appsec/iast/aspects/test_replace_aspect.py +++ b/tests/appsec/iast/aspects/test_replace_aspect.py @@ -7,9 +7,9 @@ from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import set_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from ddtrace.internal.compat import PYTHON_VERSION_INFO diff --git a/tests/appsec/iast/aspects/test_side_effects.py b/tests/appsec/iast/aspects/test_side_effects.py index 0c019f9994b..120b8f88a05 100644 --- a/tests/appsec/iast/aspects/test_side_effects.py +++ b/tests/appsec/iast/aspects/test_side_effects.py @@ -3,9 +3,9 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject -from ddtrace.appsec._iast._taint_tracking import taint_pyobject_with_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject_with_ranges import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils_side_effects import MagicMethodsException diff --git a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py index bd42b136e06..6869fbbd15c 100644 --- a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py @@ -5,10 +5,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_split_aspect.py b/tests/appsec/iast/aspects/test_split_aspect.py index 30f4fe121ca..faee670d12e 100644 --- a/tests/appsec/iast/aspects/test_split_aspect.py +++ b/tests/appsec/iast/aspects/test_split_aspect.py @@ -9,11 +9,11 @@ from ddtrace.appsec._iast._taint_tracking import _aspect_rsplit from ddtrace.appsec._iast._taint_tracking import _aspect_split from ddtrace.appsec._iast._taint_tracking import _aspect_splitlines -from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import set_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.test_aspect_helpers import _build_sample_range from tests.utils import override_global_config diff --git a/tests/appsec/iast/aspects/test_str_aspect.py b/tests/appsec/iast/aspects/test_str_aspect.py index ba32fa970b5..c036a5fbbe3 100644 --- a/tests/appsec/iast/aspects/test_str_aspect.py +++ b/tests/appsec/iast/aspects/test_str_aspect.py @@ -6,9 +6,9 @@ from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.aspect_utils import create_taint_range_with_format diff --git a/tests/appsec/iast/fixtures/entrypoint/views.py b/tests/appsec/iast/fixtures/entrypoint/views.py index 58baf906c53..3359e5c0366 100644 --- a/tests/appsec/iast/fixtures/entrypoint/views.py +++ b/tests/appsec/iast/fixtures/entrypoint/views.py @@ -3,9 +3,9 @@ def add_test(): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import create_context - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._context import create_context + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject string_to_taint = "abc" create_context() diff --git a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_psycopg2.py b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_psycopg2.py index 3411509c956..69994a20fe1 100644 --- a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_psycopg2.py +++ b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_psycopg2.py @@ -3,8 +3,8 @@ import psycopg2 from psycopg2.errors import DuplicateTable -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted POSTGRES_HOST = os.getenv("TEST_POSTGRES_HOST", "127.0.0.1") diff --git a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlalchemy.py b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlalchemy.py index f8910b6b5cd..29b014ee364 100644 --- a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlalchemy.py +++ b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlalchemy.py @@ -2,8 +2,8 @@ from sqlalchemy import text from sqlalchemy.exc import ProgrammingError -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted def sqli_simple(table): diff --git a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlite3.py b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlite3.py index 08d98abf24d..3d8ee69f8b3 100644 --- a/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlite3.py +++ b/tests/appsec/iast/fixtures/taint_sinks/sql_injection_sqlite3.py @@ -1,7 +1,7 @@ import sqlite3 -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted def sqli_simple(table): diff --git a/tests/appsec/iast/taint_sinks/test_command_injection.py b/tests/appsec/iast/taint_sinks/test_command_injection.py index a18fac45de1..b716f594e85 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection.py @@ -7,8 +7,8 @@ from ddtrace.appsec._iast._iast_request_context import get_iast_reporter from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.appsec._iast.taint_sinks.command_injection import patch diff --git a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py index f1e2b98089c..4c25cda8dc2 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py @@ -3,7 +3,7 @@ from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.appsec._iast.reporter import Evidence diff --git a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py index d47433f7745..61a3aa83a49 100644 --- a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py @@ -2,10 +2,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.reporter import Evidence diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal.py b/tests/appsec/iast/taint_sinks/test_path_traversal.py index b195edc2427..cc016eb29fb 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal.py @@ -4,7 +4,7 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py index 181af423c9c..996bc2ee356 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py @@ -4,7 +4,7 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.appsec._iast.reporter import Evidence from ddtrace.appsec._iast.reporter import IastSpanReporter diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection.py b/tests/appsec/iast/taint_sinks/test_sql_injection.py index d8fe767efb6..bf2190cdf99 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection.py @@ -1,8 +1,8 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from tests.appsec.iast.aspects.conftest import _iast_patched_module diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index ba6675e7531..01645cf1d39 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -1,10 +1,10 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.reporter import Evidence diff --git a/tests/appsec/iast/taint_sinks/test_ssrf.py b/tests/appsec/iast/taint_sinks/test_ssrf.py index 8b35013b873..f6f3ea0fb58 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf.py @@ -1,5 +1,5 @@ from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.contrib.httplib.patch import patch as httplib_patch diff --git a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py index aa316ab3b02..d5f60e8878e 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py @@ -4,7 +4,7 @@ from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.appsec._iast.reporter import Evidence diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range.py b/tests/appsec/iast/taint_tracking/test_native_taint_range.py index 00079d7772b..d1b862b73a3 100644 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range.py +++ b/tests/appsec/iast/taint_tracking/test_native_taint_range.py @@ -14,19 +14,19 @@ from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import are_all_text_all_ranges -from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import debug_taint_map from ddtrace.appsec._iast._taint_tracking import get_range_by_hash from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._taint_tracking import is_notinterned_notfasttainted_unicode from ddtrace.appsec._iast._taint_tracking import num_objects_tainted -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import reset_contexts -from ddtrace.appsec._iast._taint_tracking import set_fast_tainted_if_notinterned_unicode from ddtrace.appsec._iast._taint_tracking import set_ranges from ddtrace.appsec._iast._taint_tracking import shift_taint_range from ddtrace.appsec._iast._taint_tracking import shift_taint_ranges -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._context import reset_contexts +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import is_notinterned_notfasttainted_unicode +from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import set_fast_tainted_if_notinterned_unicode +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast._taint_tracking.aspects import bytearray_extend_aspect as extend_aspect from ddtrace.appsec._iast._taint_tracking.aspects import format_aspect diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index ac3d009633f..0844b24fbd8 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -13,9 +13,9 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import num_objects_tainted - from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import set_ranges - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._context import reset_context + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect diff --git a/tests/appsec/iast/test_env_var.py b/tests/appsec/iast/test_env_var.py index 57604815aac..18fe79229ed 100644 --- a/tests/appsec/iast/test_env_var.py +++ b/tests/appsec/iast/test_env_var.py @@ -127,13 +127,6 @@ def test_env_var_iast_disabled_parametrized(capfd, configuration_endpoint, env_v assert "IAST enabled" not in captured.err -@pytest.mark.subprocess( - env=dict(DD_IAST_ENABLED="False"), err=b"WARNING:root:IAST not enabled but native module is being loaded\n" -) -def test_env_var_iast_disabled_native_module_warning(): - import ddtrace.appsec._iast._taint_tracking._native # noqa: F401 - - @pytest.mark.subprocess(env=dict(DD_IAST_ENABLED="True"), err=None) def test_env_var_iast_enabled_no__native_module_warning(): import ddtrace.appsec._iast._taint_tracking._native # noqa: F401 diff --git a/tests/appsec/iast/test_grpc_iast.py b/tests/appsec/iast/test_grpc_iast.py index 47104e0915e..ba7b6027759 100644 --- a/tests/appsec/iast/test_grpc_iast.py +++ b/tests/appsec/iast/test_grpc_iast.py @@ -28,7 +28,7 @@ def iast_c_context(): def _check_test_range(value): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges = get_tainted_ranges(value) assert len(ranges) == 1, f"found {len(ranges)} ranges" diff --git a/tests/appsec/iast/test_iast_propagation_path.py b/tests/appsec/iast/test_iast_propagation_path.py index c9c32b7258e..229e3abbc55 100644 --- a/tests/appsec/iast/test_iast_propagation_path.py +++ b/tests/appsec/iast/test_iast_propagation_path.py @@ -2,7 +2,7 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils import get_line_and_hash diff --git a/tests/appsec/iast/test_json_tainting.py b/tests/appsec/iast/test_json_tainting.py index 2678fd70487..43c9370f306 100644 --- a/tests/appsec/iast/test_json_tainting.py +++ b/tests/appsec/iast/test_json_tainting.py @@ -3,9 +3,9 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_utils import LazyTaintDict from ddtrace.appsec._iast._taint_utils import LazyTaintList from tests.utils import override_global_config diff --git a/tests/appsec/iast/test_taint_utils.py b/tests/appsec/iast/test_taint_utils.py index 6749c2788ec..9e32b0e4049 100644 --- a/tests/appsec/iast/test_taint_utils.py +++ b/tests/appsec/iast/test_taint_utils.py @@ -2,8 +2,8 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_utils import LazyTaintDict from ddtrace.appsec._iast._taint_utils import LazyTaintList from ddtrace.appsec._iast._taint_utils import check_tainted_dbapi_args diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 95b9b8aeb45..dc07754bdc5 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -14,7 +14,7 @@ from ddtrace.appsec._iast._patch_modules import patch_iast from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import origin_to_str -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL diff --git a/tests/appsec/iast_memcheck/test_iast_mem_check.py b/tests/appsec/iast_memcheck/test_iast_mem_check.py index d427f124aae..c049dd3c572 100644 --- a/tests/appsec/iast_memcheck/test_iast_mem_check.py +++ b/tests/appsec/iast_memcheck/test_iast_mem_check.py @@ -7,12 +7,12 @@ from ddtrace.appsec._iast._stacktrace import get_info_frame from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size -from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import initializer_size from ddtrace.appsec._iast._taint_tracking import num_objects_tainted -from ddtrace.appsec._iast._taint_tracking import reset_context -from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking._context import create_context +from ddtrace.appsec._iast._taint_tracking._context import reset_context +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.taint_sinks.conftest import _get_span_report from tests.appsec.iast_memcheck._stacktrace_py import get_info_frame as get_info_frame_py diff --git a/tests/appsec/iast_packages/packages/pkg_attrs.py b/tests/appsec/iast_packages/packages/pkg_attrs.py index 2d32ce4b7a2..fb38d8e3237 100644 --- a/tests/appsec/iast_packages/packages/pkg_attrs.py +++ b/tests/appsec/iast_packages/packages/pkg_attrs.py @@ -37,7 +37,7 @@ class User: def pkg_attrs_propagation_view(): import attrs - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py b/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py index b6c55056165..d99f5f63eb2 100644 --- a/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py +++ b/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py @@ -30,7 +30,7 @@ def pkg_beautifulsoup4_view(): def pkg_beautifulsoup4_propagation_view(): from bs4 import BeautifulSoup - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_cachetools.py b/tests/appsec/iast_packages/packages/pkg_cachetools.py index 53805009867..5c3ef483f10 100644 --- a/tests/appsec/iast_packages/packages/pkg_cachetools.py +++ b/tests/appsec/iast_packages/packages/pkg_cachetools.py @@ -50,7 +50,7 @@ def expensive_function(key): def pkg_cachetools_propagation_view(): import cachetools - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_chartset_normalizer.py b/tests/appsec/iast_packages/packages/pkg_chartset_normalizer.py index e98d3547ad3..073ec6dd1f9 100644 --- a/tests/appsec/iast_packages/packages/pkg_chartset_normalizer.py +++ b/tests/appsec/iast_packages/packages/pkg_chartset_normalizer.py @@ -25,7 +25,7 @@ def pkg_charset_normalizer_view(): def pkg_charset_normalizer_propagation_view(): from charset_normalizer import from_bytes - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_cryptography.py b/tests/appsec/iast_packages/packages/pkg_cryptography.py index cf34079fce7..79e020e5863 100644 --- a/tests/appsec/iast_packages/packages/pkg_cryptography.py +++ b/tests/appsec/iast_packages/packages/pkg_cryptography.py @@ -41,7 +41,7 @@ def pkg_cryptography_view(): def pkg_cryptography_propagation_view(): from cryptography.fernet import Fernet - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_docutils.py b/tests/appsec/iast_packages/packages/pkg_docutils.py index 971fbacdd50..ff218f8fc17 100644 --- a/tests/appsec/iast_packages/packages/pkg_docutils.py +++ b/tests/appsec/iast_packages/packages/pkg_docutils.py @@ -43,7 +43,7 @@ def pkg_docutils_view(): def pkg_docutils_propagation_view(): import docutils.core - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_exceptiongroup.py b/tests/appsec/iast_packages/packages/pkg_exceptiongroup.py index 4f1786237e2..2aa092304c3 100644 --- a/tests/appsec/iast_packages/packages/pkg_exceptiongroup.py +++ b/tests/appsec/iast_packages/packages/pkg_exceptiongroup.py @@ -46,7 +46,7 @@ def raise_exceptions(param): def pkg_exceptiongroup_propagation_view(): from exceptiongroup import ExceptionGroup - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) try: diff --git a/tests/appsec/iast_packages/packages/pkg_idna.py b/tests/appsec/iast_packages/packages/pkg_idna.py index 1421d5c2dcf..19ace566034 100644 --- a/tests/appsec/iast_packages/packages/pkg_idna.py +++ b/tests/appsec/iast_packages/packages/pkg_idna.py @@ -27,7 +27,7 @@ def pkg_idna_view(): def pkg_idna_propagation_view(): import idna - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_iniconfig.py b/tests/appsec/iast_packages/packages/pkg_iniconfig.py index 4f204d7ee54..8ecf2c52b98 100644 --- a/tests/appsec/iast_packages/packages/pkg_iniconfig.py +++ b/tests/appsec/iast_packages/packages/pkg_iniconfig.py @@ -50,7 +50,7 @@ def pkg_iniconfig_view(): def pkg_iniconfig_propagation_view(): import iniconfig - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) try: diff --git a/tests/appsec/iast_packages/packages/pkg_jinja2.py b/tests/appsec/iast_packages/packages/pkg_jinja2.py index acedfff0d1e..4699d2f85a2 100644 --- a/tests/appsec/iast_packages/packages/pkg_jinja2.py +++ b/tests/appsec/iast_packages/packages/pkg_jinja2.py @@ -36,7 +36,7 @@ def pkg_jinja2_view(): def pkg_jinja2_propagation_view(): from jinja2 import Template - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_lxml.py b/tests/appsec/iast_packages/packages/pkg_lxml.py index 3309731f197..81a59e5758b 100644 --- a/tests/appsec/iast_packages/packages/pkg_lxml.py +++ b/tests/appsec/iast_packages/packages/pkg_lxml.py @@ -39,7 +39,7 @@ def pkg_lxml_view(): def pkg_lxml_propagation_view(): from lxml import etree - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_multidict.py b/tests/appsec/iast_packages/packages/pkg_multidict.py index f0cbe10f028..b07a5c69731 100644 --- a/tests/appsec/iast_packages/packages/pkg_multidict.py +++ b/tests/appsec/iast_packages/packages/pkg_multidict.py @@ -36,7 +36,7 @@ def pkg_multidict_view(): def pkg_multidict_propagation_view(): from multidict import MultiDict - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_platformdirs.py b/tests/appsec/iast_packages/packages/pkg_platformdirs.py index 838c0a20e01..325a0177cbf 100644 --- a/tests/appsec/iast_packages/packages/pkg_platformdirs.py +++ b/tests/appsec/iast_packages/packages/pkg_platformdirs.py @@ -47,7 +47,7 @@ def pkg_platformdirs_view(): def pkg_platformdirs_propagation_view(): from platformdirs import user_data_dir - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_pyasn1.py b/tests/appsec/iast_packages/packages/pkg_pyasn1.py index 8e64024ad3c..3f870889f07 100644 --- a/tests/appsec/iast_packages/packages/pkg_pyasn1.py +++ b/tests/appsec/iast_packages/packages/pkg_pyasn1.py @@ -52,7 +52,7 @@ def pkg_pyasn1_propagation_view(): from pyasn1.type import namedtype from pyasn1.type import univ - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_pygments.py b/tests/appsec/iast_packages/packages/pkg_pygments.py index 6cde162a6bd..8466cb40333 100644 --- a/tests/appsec/iast_packages/packages/pkg_pygments.py +++ b/tests/appsec/iast_packages/packages/pkg_pygments.py @@ -45,7 +45,7 @@ def pkg_pygments_propagation_view(): from pygments.formatters import HtmlFormatter from pygments.lexers import PythonLexer - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_pynacl.py b/tests/appsec/iast_packages/packages/pkg_pynacl.py index 78c8baadb3a..1e8435d050f 100644 --- a/tests/appsec/iast_packages/packages/pkg_pynacl.py +++ b/tests/appsec/iast_packages/packages/pkg_pynacl.py @@ -53,7 +53,7 @@ def pkg_pynacl_propagation_view(): from nacl import secret from nacl import utils - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_pyparsing.py b/tests/appsec/iast_packages/packages/pkg_pyparsing.py index bcc1647adb7..b5defd3c2ec 100644 --- a/tests/appsec/iast_packages/packages/pkg_pyparsing.py +++ b/tests/appsec/iast_packages/packages/pkg_pyparsing.py @@ -47,7 +47,7 @@ def pkg_pyparsing_view(): def pkg_pyparsing_propagation_view(): import pyparsing as pp - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_python_multipart.py b/tests/appsec/iast_packages/packages/pkg_python_multipart.py index b8fab3ec159..0f0b79e5b85 100644 --- a/tests/appsec/iast_packages/packages/pkg_python_multipart.py +++ b/tests/appsec/iast_packages/packages/pkg_python_multipart.py @@ -33,7 +33,7 @@ def pkg_multipart_view(): def pkg_multipart_propagation_view(): from multipart.multipart import parse_options_header - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_pyyaml.py b/tests/appsec/iast_packages/packages/pkg_pyyaml.py index 7d394c998f3..93a37976ed1 100644 --- a/tests/appsec/iast_packages/packages/pkg_pyyaml.py +++ b/tests/appsec/iast_packages/packages/pkg_pyyaml.py @@ -30,7 +30,7 @@ def pkg_pyyaml_view(): def pkg_pyyaml_propagation_view(): import yaml - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_rsa.py b/tests/appsec/iast_packages/packages/pkg_rsa.py index 209b2aef783..b921f18c33a 100644 --- a/tests/appsec/iast_packages/packages/pkg_rsa.py +++ b/tests/appsec/iast_packages/packages/pkg_rsa.py @@ -36,7 +36,7 @@ def pkg_rsa_view(): def pkg_rsa_propagation_view(): import rsa - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_soupsieve.py b/tests/appsec/iast_packages/packages/pkg_soupsieve.py index a4017bdcde2..eba3539e318 100644 --- a/tests/appsec/iast_packages/packages/pkg_soupsieve.py +++ b/tests/appsec/iast_packages/packages/pkg_soupsieve.py @@ -43,7 +43,7 @@ def pkg_soupsieve_propagation_view(): from bs4 import BeautifulSoup import soupsieve as sv - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_sqlalchemy.py b/tests/appsec/iast_packages/packages/pkg_sqlalchemy.py index 09fe47336fb..17daa4b5405 100644 --- a/tests/appsec/iast_packages/packages/pkg_sqlalchemy.py +++ b/tests/appsec/iast_packages/packages/pkg_sqlalchemy.py @@ -58,7 +58,7 @@ def pkg_sqlalchemy_propagation_view(): from sqlalchemy import create_engine from sqlalchemy.orm import declarative_base - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_tomli.py b/tests/appsec/iast_packages/packages/pkg_tomli.py index 7741aeea8bc..f21aa2e8a54 100644 --- a/tests/appsec/iast_packages/packages/pkg_tomli.py +++ b/tests/appsec/iast_packages/packages/pkg_tomli.py @@ -38,7 +38,7 @@ def pkg_tomli_view(): def pkg_tomli_propagation_view(): import tomli - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/pkg_wrapt.py b/tests/appsec/iast_packages/packages/pkg_wrapt.py index be624bc399b..a58504f294c 100644 --- a/tests/appsec/iast_packages/packages/pkg_wrapt.py +++ b/tests/appsec/iast_packages/packages/pkg_wrapt.py @@ -46,7 +46,7 @@ def sample_function(param): @pkg_wrapt.route("/wrapt_propagation") def pkg_wrapt_propagation_view(): - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) diff --git a/tests/appsec/iast_packages/packages/pkg_yarl.py b/tests/appsec/iast_packages/packages/pkg_yarl.py index 02940a240ea..9e254aab2c7 100644 --- a/tests/appsec/iast_packages/packages/pkg_yarl.py +++ b/tests/appsec/iast_packages/packages/pkg_yarl.py @@ -44,7 +44,7 @@ def pkg_yarl_view(): def pkg_yarl_propagation_view(): from yarl import URL - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted response = ResultResponse(request.args.get("package_param")) if not is_pyobject_tainted(response.package_param): diff --git a/tests/appsec/iast_packages/packages/utils.py b/tests/appsec/iast_packages/packages/utils.py index c36c6966f9e..7c6d4c95f5b 100644 --- a/tests/appsec/iast_packages/packages/utils.py +++ b/tests/appsec/iast_packages/packages/utils.py @@ -2,7 +2,7 @@ with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted class ResultResponse: diff --git a/tests/appsec/iast_tdd_propagation/flask_orm_app.py b/tests/appsec/iast_tdd_propagation/flask_orm_app.py index b7fcf9f59c2..b4e7e0d2095 100644 --- a/tests/appsec/iast_tdd_propagation/flask_orm_app.py +++ b/tests/appsec/iast_tdd_propagation/flask_orm_app.py @@ -18,7 +18,7 @@ with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted import ddtrace.auto # noqa: F401 # isort: skip diff --git a/tests/appsec/iast_tdd_propagation/flask_propagation_views.py b/tests/appsec/iast_tdd_propagation/flask_propagation_views.py index 0cf9f201d7f..ae1ce2af489 100644 --- a/tests/appsec/iast_tdd_propagation/flask_propagation_views.py +++ b/tests/appsec/iast_tdd_propagation/flask_propagation_views.py @@ -4,7 +4,7 @@ from flask import request from ddtrace import tracer -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted class ResultResponse: diff --git a/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py b/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py index 56074989bc5..396aa0db63c 100644 --- a/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py +++ b/tests/appsec/iast_tdd_propagation/flask_taint_sinks_views.py @@ -6,7 +6,7 @@ from flask import request from ddtrace import tracer -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from tests.appsec.iast.taint_sinks.conftest import _get_span_report diff --git a/tests/appsec/integrations/pygoat_tests/test_pygoat.py b/tests/appsec/integrations/pygoat_tests/test_pygoat.py index f3dd0f173ee..8bb8baae1bd 100644 --- a/tests/appsec/integrations/pygoat_tests/test_pygoat.py +++ b/tests/appsec/integrations/pygoat_tests/test_pygoat.py @@ -143,7 +143,7 @@ def test_sqli(client): @pytest.mark.skip("TODO: SSRF is not implemented for open()") def test_ssrf1(client, iast_context_defaults): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject s = "templates/Lab/ssrf/blogs/blog2.txt" tainted_path = taint_pyobject( @@ -160,7 +160,7 @@ def test_ssrf1(client, iast_context_defaults): def test_ssrf2(client, iast_context_defaults): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject s = "http://example.com" tainted_path = taint_pyobject( diff --git a/tests/appsec/integrations/test_flask_iast_patching.py b/tests/appsec/integrations/test_flask_iast_patching.py index 5dd1baab67c..3291297ea92 100644 --- a/tests/appsec/integrations/test_flask_iast_patching.py +++ b/tests/appsec/integrations/test_flask_iast_patching.py @@ -18,7 +18,7 @@ def test_flask_iast_ast_patching_import_error(): pass """ with flask_server( - appsec_enabled="false", iast_enabled="true", token=None, port=_PORT, assert_debug=True + appsec_enabled="false", iast_enabled="true", token=None, port=_PORT, assert_debug=False ) as context: _, flask_client, pid = context diff --git a/tests/appsec/integrations/test_langchain.py b/tests/appsec/integrations/test_langchain.py index 795d48db8b9..cf0dca1e49e 100644 --- a/tests/appsec/integrations/test_langchain.py +++ b/tests/appsec/integrations/test_langchain.py @@ -14,7 +14,7 @@ with override_env({"DD_IAST_ENABLED": "True"}): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject @pytest.mark.skipif(not is_module_installed("langchain"), reason="Langchain tests work on 3.9 or higher") diff --git a/tests/appsec/integrations/test_psycopg2.py b/tests/appsec/integrations/test_psycopg2.py index 3e08670f2d1..d6d25f7ffc2 100644 --- a/tests/appsec/integrations/test_psycopg2.py +++ b/tests/appsec/integrations/test_psycopg2.py @@ -2,7 +2,7 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_utils import LazyTaintList from tests.appsec.iast.conftest import _end_iast_context_and_oce from tests.appsec.iast.conftest import _start_iast_context_and_oce diff --git a/tests/contrib/dbapi/test_dbapi_appsec.py b/tests/contrib/dbapi/test_dbapi_appsec.py index 0ee86f99685..b60b3ac05c0 100644 --- a/tests/contrib/dbapi/test_dbapi_appsec.py +++ b/tests/contrib/dbapi/test_dbapi_appsec.py @@ -36,7 +36,7 @@ def tearDown(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query(self): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject with override_global_config( dict( @@ -59,7 +59,7 @@ def test_tainted_query(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query_args(self): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" @@ -113,7 +113,7 @@ def test_untainted_query_and_args(self): @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query_iast_disabled(self): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject with mock.patch( "ddtrace.appsec._iast.taint_sinks.sql_injection.SqlInjection.report" diff --git a/tests/contrib/django/django_app/appsec_urls.py b/tests/contrib/django/django_app/appsec_urls.py index 7ded023460f..f5b3f359445 100644 --- a/tests/contrib/django/django_app/appsec_urls.py +++ b/tests/contrib/django/django_app/appsec_urls.py @@ -9,19 +9,13 @@ from ddtrace import tracer from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import decode_aspect from ddtrace.appsec._iast._utils import _is_python_version_supported as python_supported_by_iast from ddtrace.appsec._trace_utils import block_request_if_user_blocked from tests.utils import override_env -try: - with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect - from ddtrace.appsec._iast._taint_tracking.aspects import decode_aspect -except ImportError: - # Python 2 compatibility - from operator import add as add_aspect - # django.conf.urls.url was deprecated in django 3 and removed in django 4 if django.VERSION < (4, 0, 0): from django.conf.urls import url as handler @@ -80,9 +74,14 @@ def checkuser_view(request, user_id): def sqli_http_request_parameter(request): + import bcrypt + from django.contrib.auth.hashers import BCryptSHA256PasswordHasher + + password_django = BCryptSHA256PasswordHasher() + obj = password_django.encode("i'm a password", bcrypt.gensalt()) with connection.cursor() as cursor: # label iast_enabled_sqli_http_request_parameter - cursor.execute(request.GET["q"]) + cursor.execute(add_aspect(add_aspect(request.GET["q"], obj), "'")) return HttpResponse(request.META["HTTP_USER_AGENT"], status=200) @@ -123,7 +122,7 @@ def taint_checking_enabled_view(request): if python_supported_by_iast(): with override_env({"DD_IAST_ENABLED": "True"}): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast.reporter import IastSpanReporter def assert_origin_path(path): # type: (Any) -> None @@ -155,7 +154,7 @@ def is_pyobject_tainted(pyobject): # type: (Any) -> bool def taint_checking_disabled_view(request): if python_supported_by_iast(): with override_env({"DD_IAST_ENABLED": "True"}): - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted else: def is_pyobject_tainted(pyobject): # type: (Any) -> bool diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index efe0fa9acd0..7e42e8aa903 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -204,14 +204,14 @@ def test_django_tainted_user_agent_iast_disabled(client, test_spans, tracer): @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): root_span, response = _aux_appsec_get_root_span( client, test_spans, tracer, payload=urlencode({"mytestingbody_key": "mytestingbody_value"}), content_type="application/x-www-form-urlencoded", - url="/appsec/sqli_http_request_parameter/?q=SELECT 1 FROM sqlite_master", + url="/appsec/sqli_http_request_parameter/?q=SELECT 1 FROM sqlite_master WHERE name='", headers={"HTTP_USER_AGENT": "test/1.2.3"}, ) @@ -228,7 +228,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(clie { "name": "q", "origin": "http.request.parameter", - "pattern": "abcdefghijklmnopqrstuvwxyzA", + "pattern": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN", "redacted": True, } ] @@ -238,7 +238,9 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(clie "valueParts": [ {"source": 0, "value": "SELECT "}, {"pattern": "h", "redacted": True, "source": 0}, - {"source": 0, "value": " FROM sqlite_master"}, + {"source": 0, "value": " FROM sqlite_master WHERE name='"}, + {"redacted": True}, + {"value": "'"}, ] } assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 7f1a140ffc2..1a5db995af4 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -74,8 +74,8 @@ def check_native_code_exception_in_each_fastapi_test(request, caplog, telemetry_ def test_query_param_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -109,8 +109,8 @@ async def test_route(request: Request): def test_header_value_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.headers.get("iast_header") ranges_result = get_tainted_ranges(query_params) @@ -146,8 +146,8 @@ async def test_route(request: Request): def test_header_value_source_typing_param(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(iast_header: typing.Annotated[str, Header()] = None): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(iast_header) @@ -180,8 +180,8 @@ async def test_route(iast_header: typing.Annotated[str, Header()] = None): def test_cookies_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.cookies.get("iast_cookie") ranges_result = get_tainted_ranges(query_params) @@ -216,8 +216,8 @@ async def test_route(request: Request): def test_cookies_source_typing_param(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(iast_cookie: typing.Annotated[str, Cookie()] = "ddd"): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(iast_cookie) @@ -250,8 +250,8 @@ async def test_route(iast_cookie: typing.Annotated[str, Cookie()] = "ddd"): def test_path_param_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html/{item_id}") async def test_route(item_id): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(item_id) @@ -283,8 +283,8 @@ async def test_route(item_id): def test_path_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/path_source/") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges path = request.url.path ranges_result = get_tainted_ranges(path) @@ -317,8 +317,8 @@ async def test_route(request: Request): def test_path_body_receive_source(fastapi_application, client, tracer, test_spans): @fastapi_application.post("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges body = await request.receive() result = body["body"] @@ -354,8 +354,8 @@ async def test_route(request: Request): def test_path_body_body_source(fastapi_application, client, tracer, test_spans): @fastapi_application.post("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges body = await request.body() ranges_result = get_tainted_ranges(body) @@ -392,8 +392,8 @@ async def test_route(request: Request): def test_path_body_body_source_formdata_latest(fastapi_application, client, tracer, test_spans): @fastapi_application.post("/index.html") async def test_route(path: typing.Annotated[str, Form()]): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(path) @@ -423,8 +423,8 @@ async def test_route(path: typing.Annotated[str, Form()]): def test_path_body_body_source_formdata_90(fastapi_application, client, tracer, test_spans): @fastapi_application.post("/index.html") async def test_route(path: str = Form(...)): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(path) @@ -463,8 +463,8 @@ class Item(BaseModel): @fastapi_application.post("/index") async def test_route(item: Item): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(item.name) @@ -497,7 +497,7 @@ async def test_route(item: Item): def test_path_body_body_upload(fastapi_application, client, tracer, test_spans): @fastapi_application.post("/uploadfile/") async def create_upload_file(files: typing.List[UploadFile]): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges ranges_result = get_tainted_ranges(files[0]) return JSONResponse( @@ -529,7 +529,7 @@ def test_fastapi_sqli_path_param(fastapi_application, client, tracer, test_spans async def test_route(param_str): import sqlite3 - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect assert is_pyobject_tainted(param_str) @@ -577,8 +577,8 @@ async def test_route(param_str): def test_fasapi_insecure_cookie(fastapi_application, client, tracer, test_spans): @fastapi_application.route("/insecure_cookie/", methods=["GET"]) def insecure_cookie(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -618,8 +618,8 @@ def insecure_cookie(request: Request): def test_fasapi_insecure_cookie_empty(fastapi_application, client, tracer, test_spans): @fastapi_application.route("/insecure_cookie/", methods=["GET"]) def insecure_cookie(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -653,8 +653,8 @@ def insecure_cookie(request: Request): def test_fasapi_no_http_only_cookie(fastapi_application, client, tracer, test_spans): @fastapi_application.route("/insecure_cookie/", methods=["GET"]) def insecure_cookie(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -694,8 +694,8 @@ def insecure_cookie(request: Request): def test_fasapi_no_http_only_cookie_empty(fastapi_application, client, tracer, test_spans): @fastapi_application.route("/insecure_cookie/", methods=["GET"]) def insecure_cookie(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -729,8 +729,8 @@ def insecure_cookie(request: Request): def test_fasapi_no_samesite_cookie(fastapi_application, client, tracer, test_spans): @fastapi_application.route("/insecure_cookie/", methods=["GET"]) def insecure_cookie(request: Request): - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import origin_to_str + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index f1bed61cb9d..238d0630549 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -59,7 +59,7 @@ def test_flask_full_sqli_iast_http_request_path_parameter(self): def sqli_1(param_str): import sqlite3 - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect assert is_pyobject_tainted(param_str) @@ -273,8 +273,8 @@ def sqli_5(param_str, param_int): from flask import request from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted header_ranges = get_tainted_ranges(request.headers["User-Agent"]) assert header_ranges @@ -324,7 +324,7 @@ def test_flask_simple_iast_path_header_and_querystring_tainted_request_sampling_ def sqli_6(param_str): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted # Note: these are not tainted because of request sampling at 0% assert not is_pyobject_tainted(request.headers["User-Agent"]) @@ -536,7 +536,7 @@ def sqli_10(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect con = sqlite3.connect(":memory:") @@ -601,7 +601,7 @@ def sqli_11(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect con = sqlite3.connect(":memory:") @@ -666,7 +666,7 @@ def sqli_11(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect con = sqlite3.connect(":memory:") @@ -731,7 +731,7 @@ def sqli_11(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect con = sqlite3.connect(":memory:") @@ -798,7 +798,7 @@ def sqli_11(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect def iterate_json(data, parent_key=""): @@ -939,7 +939,7 @@ def sqli_10(): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect con = sqlite3.connect(":memory:") @@ -1042,7 +1042,7 @@ def header_injection(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1082,7 +1082,7 @@ def header_injection(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1111,7 +1111,7 @@ def header_injection(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1140,7 +1140,7 @@ def insecure_cookie(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1178,7 +1178,7 @@ def insecure_cookie_empty(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1208,7 +1208,7 @@ def no_http_only_cookie(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1246,7 +1246,7 @@ def no_http_only_cookie_empty(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1277,7 +1277,7 @@ def no_samesite_cookie(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1315,7 +1315,7 @@ def no_samesite_cookie_empty(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1343,7 +1343,7 @@ def cookie_secure(): from flask import Response from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted tainted_string = request.form.get("name") assert is_pyobject_tainted(tainted_string) @@ -1518,7 +1518,7 @@ def test_flask_simple_iast_path_header_and_querystring_not_tainted_if_iast_disab def test_sqli(param_str): from flask import request - from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted assert not is_pyobject_tainted(request.headers["User-Agent"]) assert not is_pyobject_tainted(request.query_string) diff --git a/tests/smoke_test.py b/tests/smoke_test.py index cbf5ebc8e61..24017c0df81 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -37,14 +37,8 @@ def emit(self, record): try: from ddtrace.appsec._iast._taint_tracking._native import ops - if os.environ.get("DD_IAST_ENABLED") == "False": - assert any( - "IAST not enabled but native module is being loaded" in message - for message in log_messages - ) - else: - assert ops - assert len(log_messages) == 0 + assert ops + assert len(log_messages) == 0 except ImportError as e: assert False, "Importing the native module failed, _native probably not compiled correctly: %s" % str(e) """ From 998ee2ceb0c35e853a72fba39ec040091eaed075 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Mon, 23 Dec 2024 10:59:12 -0500 Subject: [PATCH 362/372] chore(tracing): move mini agent out of tracing (#11814) ## Description No idea where we should put starting the mini-agent but doing it in tracer initialization feels wrong. The mini agent should only be started once (at most). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/__init__.py | 5 +++++ ddtrace/_trace/tracer.py | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 1954c1961c9..1f2049cd0a5 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -33,6 +33,11 @@ from .version import get_version # noqa: E402 +# TODO(mabdinur): Remove this once we have a better way to start the mini agent +from ddtrace.internal.serverless.mini_agent import maybe_start_serverless_mini_agent as _start_mini_agent + +_start_mini_agent() + # DEV: Import deprecated tracer module in order to retain side-effect of package # initialization, which added this module to sys.modules. We catch deprecation # warnings as this is only to retain a side effect of the package diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 6027976d6dc..fa0c89cdd7f 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -53,7 +53,6 @@ from ddtrace.internal.serverless import in_aws_lambda from ddtrace.internal.serverless import in_azure_function from ddtrace.internal.serverless import in_gcp_function -from ddtrace.internal.serverless.mini_agent import maybe_start_serverless_mini_agent from ddtrace.internal.service import ServiceStatusError from ddtrace.internal.utils import _get_metas_to_propagate from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning @@ -211,8 +210,6 @@ def __init__( :param dogstatsd_url: The DogStatsD URL. """ - maybe_start_serverless_mini_agent() - self._filters: List[TraceFilter] = [] # globally set tags From e29ccb0a68bc73bf3d7f071e66e1c8a20eb0c801 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 26 Dec 2024 02:08:11 +0100 Subject: [PATCH 363/372] fix: error parsing response cookies in FastAPI and awsgi (#11829) This fix resolves an issue parsing response cookies in FastAPI and awsgi issue: https://github.com/DataDog/dd-trace-py/issues/11818 ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/internal/asgi/middleware.py | 22 +++++---- .../iast-fix-awsgi-368c173e1f012400.yaml | 4 ++ tests/contrib/asgi/test_asgi.py | 48 +++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/iast-fix-awsgi-368c173e1f012400.yaml diff --git a/ddtrace/contrib/internal/asgi/middleware.py b/ddtrace/contrib/internal/asgi/middleware.py index e32d2994a3d..98d352cf75f 100644 --- a/ddtrace/contrib/internal/asgi/middleware.py +++ b/ddtrace/contrib/internal/asgi/middleware.py @@ -91,6 +91,18 @@ async def _blocked_asgi_app(scope, receive, send): await send({"type": "http.response.body", "body": b""}) +def _parse_response_cookies(response_headers): + cookies = {} + try: + result = response_headers.get("set-cookie", "").split("=", maxsplit=1) + if len(result) == 2: + cookie_key, cookie_value = result + cookies[cookie_key] = cookie_value + except Exception: + log.debug("failed to extract response cookies", exc_info=True) + return cookies + + class TraceMiddleware: """ ASGI application middleware that traces the requests. @@ -211,7 +223,6 @@ async def __call__(self, scope, receive, send): peer_ip = client[0] else: peer_ip = None - trace_utils.set_http_meta( span, self.integration_config, @@ -234,15 +245,8 @@ async def wrapped_send(message): except Exception: log.warning("failed to extract response headers", exc_info=True) response_headers = None - if span and message.get("type") == "http.response.start" and "status" in message: - cookies = {} - try: - cookie_key, cookie_value = response_headers.get("set-cookie", "").split("=", maxsplit=1) - cookies[cookie_key] = cookie_value - except Exception: - log.debug("failed to extract response cookies", exc_info=True) - + cookies = _parse_response_cookies(response_headers) status_code = message["status"] trace_utils.set_http_meta( span, diff --git a/releasenotes/notes/iast-fix-awsgi-368c173e1f012400.yaml b/releasenotes/notes/iast-fix-awsgi-368c173e1f012400.yaml new file mode 100644 index 00000000000..4d40945744a --- /dev/null +++ b/releasenotes/notes/iast-fix-awsgi-368c173e1f012400.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + ASGI: This fix resolves an issue parsing response cookies in FastAPI and awsgi diff --git a/tests/contrib/asgi/test_asgi.py b/tests/contrib/asgi/test_asgi.py index 40935990fc2..c1e1f1c7328 100644 --- a/tests/contrib/asgi/test_asgi.py +++ b/tests/contrib/asgi/test_asgi.py @@ -1,5 +1,6 @@ import asyncio from functools import partial +import logging import os import random @@ -10,6 +11,7 @@ from ddtrace.constants import ERROR_MSG from ddtrace.contrib.asgi import TraceMiddleware from ddtrace.contrib.asgi import span_from_scope +from ddtrace.contrib.internal.asgi.middleware import _parse_response_cookies from ddtrace.propagation import http as http_propagation from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import DummyTracer @@ -634,6 +636,52 @@ async def test_tasks_asgi_without_more_body(scope, tracer, test_spans): assert request_span.duration < 1 +@pytest.mark.asyncio +async def test_request_parse_response_cookies(tracer, test_spans, caplog): + """ + Regression test https://github.com/DataDog/dd-trace-py/issues/11818 + """ + + async def tasks_cookies(scope, receive, send): + message = await receive() + if message.get("type") == "http.request": + await send({"type": "http.response.start", "status": 200, "headers": [[b"set-cookie", b"test_cookie"]]}) + await send({"type": "http.response.body", "body": b"*"}) + await asyncio.sleep(1) + + with caplog.at_level(logging.DEBUG): + app = TraceMiddleware(tasks_cookies, tracer=tracer) + async with httpx.AsyncClient(app=app) as client: + response = await client.get("http://testserver/") + assert response.status_code == 200 + + assert "failed to extract response cookies" not in caplog.text + + +@pytest.mark.parametrize( + "headers,expected_result", + [ + ({}, {}), + ({"cookie": "cookie1=value1"}, {}), + ({"header-1": ""}, {}), + ({"Set-cookie": "cookie1=value1"}, {}), + ({"set-Cookie": "cookie1=value1"}, {}), + ({"SET-cookie": "cookie1=value1"}, {}), + ({"set-cookie": "a"}, {}), + ({"set-cookie": "1234"}, {}), + ({"set-cookie": "cookie1=value1"}, {"cookie1": "value1"}), + ({"set-cookie": "cookie2=value1=value2"}, {"cookie2": "value1=value2"}), + ({"set-cookie": "cookie3=="}, {"cookie3": "="}), + ], +) +def test__parse_response_cookies(headers, expected_result, caplog): + with caplog.at_level(logging.DEBUG): + result = _parse_response_cookies(headers) + + assert "failed to extract response cookies" not in caplog.text + assert result == expected_result + + @pytest.mark.asyncio async def test_tasks_asgi_with_more_body(scope, tracer, test_spans): """ From a433a9842e52b71a27e34c544d1726ebbad3b355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20De=20Ara=C3=BAjo?= Date: Fri, 27 Dec 2024 08:01:01 +0000 Subject: [PATCH 364/372] chore(ci_visibility): add quarantine skipping mode for pytest (#11828) This PR adds support for a quarantine mode in which tests are skipped instead of running in background. Currently, this mode can be enabled with the `_DD_TEST_SKIP_QUARANTINED_TESTS` environment variable. In the future, this will be enabled through the settings API, though the exact setting name is not definitive yet (the code uses `quarantine.skip_quarantined_tests` for now). Additionally, this PR fixes the riot environment for `pytest` to run tests with both v1 and v2 plugin versions. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/pytest/_plugin_v2.py | 20 ++- ddtrace/internal/ci_visibility/_api_client.py | 5 +- ddtrace/internal/ci_visibility/recorder.py | 19 +++ ddtrace/internal/test_visibility/api.py | 14 ++ docs/configuration.rst | 24 ++- riotfile.py | 26 ++- .../contrib/pytest/test_pytest_quarantine.py | 152 +++++++++++++++++- 7 files changed, 248 insertions(+), 12 deletions(-) diff --git a/ddtrace/contrib/pytest/_plugin_v2.py b/ddtrace/contrib/pytest/_plugin_v2.py index cfcd109f7f9..f15373a776a 100644 --- a/ddtrace/contrib/pytest/_plugin_v2.py +++ b/ddtrace/contrib/pytest/_plugin_v2.py @@ -85,6 +85,7 @@ _NODEID_REGEX = re.compile("^((?P.*)/(?P[^/]*?))::(?P.*?)$") USER_PROPERTY_QUARANTINED = "dd_quarantined" OUTCOME_QUARANTINED = "quarantined" +SKIPPED_BY_QUARANTINE_REASON = "Skipped by Datadog Quarantine" def _handle_itr_should_skip(item, test_id) -> bool: @@ -113,6 +114,19 @@ def _handle_itr_should_skip(item, test_id) -> bool: return False +def _handle_quarantine(item, test_id): + """Add a user property to identify quarantined tests, and mark them for skipping if quarantine is enabled in + skipping mode. + """ + is_quarantined = InternalTest.is_quarantined_test(test_id) + if is_quarantined: + # We add this information to user_properties to have it available in pytest_runtest_makereport(). + item.user_properties += [(USER_PROPERTY_QUARANTINED, True)] + + if InternalTestSession.should_skip_quarantined_tests(): + item.add_marker(pytest.mark.skip(reason=SKIPPED_BY_QUARANTINE_REASON)) + + def _start_collecting_coverage() -> ModuleCodeCollector.CollectInContext: coverage_collector = ModuleCodeCollector.CollectInContext() # TODO: don't depend on internal for telemetry @@ -325,17 +339,13 @@ def _pytest_runtest_protocol_pre_yield(item) -> t.Optional[ModuleCodeCollector.C InternalTest.start(test_id) + _handle_quarantine(item, test_id) _handle_itr_should_skip(item, test_id) item_will_skip = _pytest_marked_to_skip(item) or InternalTest.was_skipped_by_itr(test_id) collect_test_coverage = InternalTestSession.should_collect_coverage() and not item_will_skip - is_quarantined = InternalTest.is_quarantined_test(test_id) - if is_quarantined: - # We add this information to user_properties to have it available in pytest_runtest_makereport(). - item.user_properties += [(USER_PROPERTY_QUARANTINED, True)] - if collect_test_coverage: return _start_collecting_coverage() diff --git a/ddtrace/internal/ci_visibility/_api_client.py b/ddtrace/internal/ci_visibility/_api_client.py index c69e00793a2..49722e51e41 100644 --- a/ddtrace/internal/ci_visibility/_api_client.py +++ b/ddtrace/internal/ci_visibility/_api_client.py @@ -92,6 +92,7 @@ class EarlyFlakeDetectionSettings: @dataclasses.dataclass(frozen=True) class QuarantineSettings: enabled: bool = False + skip_quarantined_tests: bool = False @dataclasses.dataclass(frozen=True) @@ -385,7 +386,9 @@ def fetch_settings(self) -> TestVisibilityAPISettings: quarantine = QuarantineSettings( enabled=attributes.get("quarantine", {}).get("enabled", False) - or asbool(os.getenv("_DD_TEST_FORCE_ENABLE_QUARANTINE")) + or asbool(os.getenv("_DD_TEST_FORCE_ENABLE_QUARANTINE")), + skip_quarantined_tests=attributes.get("quarantine", {}).get("skip_quarantined_tests", False) + or asbool(os.getenv("_DD_TEST_SKIP_QUARANTINED_TESTS")), ) except KeyError: diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index eb8f00d5405..609475506d3 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -455,6 +455,14 @@ def is_quarantine_enabled(cls): os.getenv("DD_TEST_QUARANTINE_ENABLED", default=True) ) + @classmethod + def should_skip_quarantined_tests(cls): + if cls._instance is None: + return False + return cls._instance._api_settings.quarantine.skip_quarantined_tests and asbool( + os.getenv("DD_TEST_QUARANTINE_ENABLED", default=True) + ) + @classmethod def should_collect_coverage(cls): return cls._instance._api_settings.coverage_enabled or asbool( @@ -1039,6 +1047,12 @@ def _on_session_should_collect_coverage() -> bool: return CIVisibility.should_collect_coverage() +@_requires_civisibility_enabled +def _on_session_should_skip_quarantined_tests() -> bool: + log.debug("Handling should skip quarantined tests") + return CIVisibility.should_skip_quarantined_tests() + + @_requires_civisibility_enabled def _on_session_get_codeowners() -> Optional[Codeowners]: log.debug("Getting codeowners") @@ -1100,6 +1114,11 @@ def _register_session_handlers(): "is_test_skipping_enabled", ) core.on("test_visibility.session.set_covered_lines_pct", _on_session_set_covered_lines_pct) + core.on( + "test_visibility.session.should_skip_quarantined_tests", + _on_session_should_skip_quarantined_tests, + "should_skip_quarantined_tests", + ) @_requires_civisibility_enabled diff --git a/ddtrace/internal/test_visibility/api.py b/ddtrace/internal/test_visibility/api.py index c4e25b29e06..4c11135b76d 100644 --- a/ddtrace/internal/test_visibility/api.py +++ b/ddtrace/internal/test_visibility/api.py @@ -116,6 +116,20 @@ def is_test_skipping_enabled() -> bool: return _is_test_skipping_enabled + @staticmethod + @_catch_and_log_exceptions + def should_skip_quarantined_tests() -> bool: + log.debug("Checking if quarantined tests should be skipped") + + should_skip = bool( + core.dispatch_with_results( + "test_visibility.session.should_skip_quarantined_tests" + ).should_skip_quarantined_tests.value + ) + log.debug("Quarantined tests should be skipped: %s", should_skip) + + return should_skip + @staticmethod @_catch_and_log_exceptions def set_covered_lines_pct(coverage_pct: float): diff --git a/docs/configuration.rst b/docs/configuration.rst index d36391de07e..35bb63fac20 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -557,10 +557,10 @@ Test Visibility default: True description: | - Configures the ``CIVisibility`` service to query the Datadog API to decide whether to enable the Datadog - `Intelligent Test Runner _`. Setting the variable to - ``false`` will skip querying the API and disable code coverage - collection and test skipping. + Configures the ``CIVisibility`` service to query the Datadog API to decide whether to enable the Datadog `Test + Impact Analysis `_ (formerly Intelligent Test + Runner). Setting the variable to ``false`` will skip querying the API and disable code coverage collection and + test skipping. version_added: v1.13.0: @@ -591,6 +591,22 @@ Test Visibility version_added: v2.16.0: + DD_PYTEST_USE_NEW_PLUGIN_BETA: + type: Boolean + default: False + + description: | + Configures the ``CIVisibility`` service to use a beta release of the new version of the pytest plugin, + supporting `Auto Test Retries `_, + `Early Flake Detection `_, and + improved coverage collection for `Test Impact Analysis + `_. This version of the plugin will become the default in + the future. See the `release notes for v2.18.0 `_ + for more information. + + version_added: + v2.18.0: + DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: type: Integer default: 500 diff --git a/riotfile.py b/riotfile.py index c674a97ac4b..69467452a1f 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1610,7 +1610,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, env={ "DD_AGENT_PORT": "9126", - "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", }, venvs=[ Venv( @@ -1637,6 +1637,18 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest": ["~=7.0", latest], "pytest-cov": "==2.12.0", }, + venvs=[ + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + }, + ), + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + }, + ), + ], ), ], ), @@ -1653,6 +1665,18 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "more_itertools": "<8.11.0", "httpx": latest, }, + venvs=[ + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "0", + }, + ), + Venv( + env={ + "DD_PYTEST_USE_NEW_PLUGIN_BETA": "1", + }, + ), + ], ), ], ), diff --git a/tests/contrib/pytest/test_pytest_quarantine.py b/tests/contrib/pytest/test_pytest_quarantine.py index 93e7fe5dbde..93b0b07eade 100644 --- a/tests/contrib/pytest/test_pytest_quarantine.py +++ b/tests/contrib/pytest/test_pytest_quarantine.py @@ -183,6 +183,8 @@ def test_quarantine_outcomes_without_atr(self): ("teardown", "passed"), ] + assert len(rec.getcalls("pytest_pyfunc_call")) == 1 + def test_quarantine_outcomes_with_atr(self): self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) @@ -211,6 +213,8 @@ def test_quarantine_outcomes_with_atr(self): ("teardown", "passed"), ] + assert len(rec.getcalls("pytest_pyfunc_call")) == 6 + def test_quarantine_fail_setup(self): self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_SETUP_QUARANTINED) @@ -222,7 +226,7 @@ def test_quarantine_fail_setup(self): assert len(self.pop_spans()) > 0 def test_quarantine_fail_teardown(self): - self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_SETUP_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_TEARDOWN_QUARANTINED) rec = self.inline_run("--ddtrace", "-q") @@ -292,3 +296,149 @@ def test_quarantine_spans_with_atr(self): [test_span_pass_quarantined] = _get_spans_from_list(spans, "test", "test_pass_quarantined") assert test_span_pass_quarantined.get_tag("test.quarantine.is_quarantined") == "true" assert test_span_pass_quarantined.get_tag("test.status") == "pass" + + +class PytestQuarantineSkippingTestCase(PytestTestCaseBase): + @pytest.fixture(autouse=True, scope="function") + def set_up_quarantine(self): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True, skip_quarantined_tests=True), + flaky_test_retries_enabled=False, + ), + ): + yield + + def test_fail_quarantined_no_ddtrace_does_not_quarantine(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_pass_normal=_TEST_PASS_UNQUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + self.testdir.makepyfile(test_fail_normal=_TEST_FAIL_UNQUARANTINED) + rec = self.inline_run("-q") + assert rec.ret == 1 + assert_stats(rec, passed=2, failed=2) + assert len(self.pop_spans()) == 0 # ddtrace disabled, not collecting traces + + def test_fail_quarantined_with_ddtrace_does_not_fail_session(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=2) + + assert len(self.pop_spans()) > 0 + + def test_failing_and_passing_quarantined_and_unquarantined_tests(self): + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_pass_normal=_TEST_PASS_UNQUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + self.testdir.makepyfile(test_fail_normal=_TEST_FAIL_UNQUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + assert rec.ret == 1 + assert_stats(rec, quarantined=2, passed=1, failed=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_outcomes_without_atr(self): + return self._test_quarantine_outcomes(atr_enabled=False) + + def test_quarantine_outcomes_with_atr(self): + return self._test_quarantine_outcomes(atr_enabled=True) + + def _test_quarantine_outcomes(self, atr_enabled): + # ATR should not retry tests skipped by quarantine. + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True, skip_quarantined_tests=True), + flaky_test_retries_enabled=atr_enabled, + ), + ): + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + outcomes = [(call.report.when, call.report.outcome) for call in rec.getcalls("pytest_report_teststatus")] + assert outcomes == [ + ("setup", "skipped"), + ("teardown", "passed"), + ] + + assert len(rec.getcalls("pytest_pyfunc_call")) == 0 # test function is not called + + def test_quarantine_fail_setup(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_SETUP_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_fail_teardown(self): + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_TEARDOWN_QUARANTINED) + + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=1) + + assert len(self.pop_spans()) > 0 + + def test_quarantine_skipping_spans_atr_disabled(self): + return self._test_quarantine_skipping_spans(atr_enabled=False) + + def test_quarantine_skipping_spans_atr_enabled(self): + return self._test_quarantine_skipping_spans(atr_enabled=True) + + def _test_quarantine_skipping_spans(self, atr_enabled): + # ATR should not affect spans for skipped quarantined tests. + self.testdir.makepyfile(test_pass_quarantined=_TEST_PASS_QUARANTINED) + self.testdir.makepyfile(test_fail_quarantined=_TEST_FAIL_QUARANTINED) + self.testdir.makepyfile(test_fail_setup_quarantined=_TEST_FAIL_SETUP_QUARANTINED) + self.testdir.makepyfile(test_fail_teardown_quarantined=_TEST_FAIL_TEARDOWN_QUARANTINED) + + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=TestVisibilityAPISettings( + quarantine=QuarantineSettings(enabled=True, skip_quarantined_tests=True), + flaky_test_retries_enabled=atr_enabled, + ), + ): + rec = self.inline_run("--ddtrace", "-q") + + assert rec.ret == 0 + assert_stats(rec, quarantined=4) + + spans = self.pop_spans() + + [session_span] = _get_spans_from_list(spans, "session") + assert session_span.get_tag("test_session.quarantine.enabled") == "true" + + [module_span] = _get_spans_from_list(spans, "module") + [suite_span_fail_quarantined] = _get_spans_from_list(spans, "suite", "test_fail_quarantined.py") + [suite_span_pass_quarantined] = _get_spans_from_list(spans, "suite", "test_pass_quarantined.py") + + [test_span_fail_quarantined] = _get_spans_from_list(spans, "test", "test_fail_quarantined") + assert test_span_fail_quarantined.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_fail_quarantined.get_tag("test.status") == "skip" + + [test_span_pass_quarantined] = _get_spans_from_list(spans, "test", "test_pass_quarantined") + assert test_span_pass_quarantined.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_pass_quarantined.get_tag("test.status") == "skip" + + [test_span_fail_setup] = _get_spans_from_list(spans, "test", "test_fail_setup") + assert test_span_fail_setup.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_fail_setup.get_tag("test.status") == "skip" + + [test_span_fail_teardown] = _get_spans_from_list(spans, "test", "test_fail_teardown") + assert test_span_fail_teardown.get_tag("test.quarantine.is_quarantined") == "true" + assert test_span_fail_teardown.get_tag("test.status") == "skip" From e472d5b9b0960d64de9cc4deacda46d3d4a9e1aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 16:48:30 +0000 Subject: [PATCH 365/372] chore: update mako latest version to 1.3.8 (#11785) Update mako lockfiles and dependency package lockfiles. This performs the following updates: 1) Some mako lockfiles use mako `latest`. This will update mako and dependencies. 2) Some mako lockfiles use a pinned (non-latest) version of mako, but require the `latest` version of another package. This will update all such packages. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: quinna-h <175135214+quinna-h@users.noreply.github.com> --- .riot/requirements/14ebf3b.txt | 18 +++++++++--------- .riot/requirements/175d0d6.txt | 16 ++++++++-------- .riot/requirements/19255aa.txt | 16 ++++++++-------- .riot/requirements/1b16023.txt | 18 +++++++++--------- .riot/requirements/1b19707.txt | 10 +++++----- .riot/requirements/1b8d922.txt | 14 +++++++------- .riot/requirements/1d5ebaf.txt | 14 +++++++------- .riot/requirements/1e53fef.txt | 20 ++++++++++---------- .riot/requirements/27d0ff8.txt | 16 ++++++++-------- .riot/requirements/b48f841.txt | 14 +++++++------- .riot/requirements/e8d8aa5.txt | 16 ++++++++-------- .riot/requirements/eda6d79.txt | 8 ++++---- 12 files changed, 90 insertions(+), 90 deletions(-) diff --git a/.riot/requirements/14ebf3b.txt b/.riot/requirements/14ebf3b.txt index c762c1a65d3..a1e350a58fc 100644 --- a/.riot/requirements/14ebf3b.txt +++ b/.riot/requirements/14ebf3b.txt @@ -4,20 +4,20 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/14ebf3b.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.5 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/175d0d6.txt b/.riot/requirements/175d0d6.txt index 2bdf82d04bc..443e7c0ee18 100644 --- a/.riot/requirements/175d0d6.txt +++ b/.riot/requirements/175d0d6.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/175d0d6.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.5 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/19255aa.txt b/.riot/requirements/19255aa.txt index 76a9c7370b5..f8375d7e478 100644 --- a/.riot/requirements/19255aa.txt +++ b/.riot/requirements/19255aa.txt @@ -4,20 +4,20 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/19255aa.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 iniconfig==2.0.0 mako==1.1.6 -markupsafe==2.1.5 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/1b16023.txt b/.riot/requirements/1b16023.txt index 3ecfcd6426c..805c3b3f36e 100644 --- a/.riot/requirements/1b16023.txt +++ b/.riot/requirements/1b16023.txt @@ -4,22 +4,22 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1b16023.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 mako==1.1.6 -markupsafe==2.1.5 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1b19707.txt b/.riot/requirements/1b19707.txt index bb31e7d6d71..5a50cb0f571 100644 --- a/.riot/requirements/1b19707.txt +++ b/.riot/requirements/1b19707.txt @@ -4,22 +4,22 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1b19707.in # -attrs==24.2.0 +attrs==24.3.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 -mako==1.3.5 +mako==1.3.8 markupsafe==2.1.5 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 zipp==3.20.2 diff --git a/.riot/requirements/1b8d922.txt b/.riot/requirements/1b8d922.txt index 76a225cb035..5b8e1001d48 100644 --- a/.riot/requirements/1b8d922.txt +++ b/.riot/requirements/1b8d922.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1b8d922.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mako==1.1.6 -markupsafe==2.1.5 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d5ebaf.txt b/.riot/requirements/1d5ebaf.txt index be7d1a0c0ef..782c31d7836 100644 --- a/.riot/requirements/1d5ebaf.txt +++ b/.riot/requirements/1d5ebaf.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1d5ebaf.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mako==1.1.6 -markupsafe==2.1.5 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e53fef.txt b/.riot/requirements/1e53fef.txt index 6908416793c..96e109a75d7 100644 --- a/.riot/requirements/1e53fef.txt +++ b/.riot/requirements/1e53fef.txt @@ -4,22 +4,22 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/1e53fef.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 hypothesis==6.45.0 importlib-metadata==8.5.0 iniconfig==2.0.0 -mako==1.3.5 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/27d0ff8.txt b/.riot/requirements/27d0ff8.txt index 291fe50cacc..aae68a87555 100644 --- a/.riot/requirements/27d0ff8.txt +++ b/.riot/requirements/27d0ff8.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/27d0ff8.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.5 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/b48f841.txt b/.riot/requirements/b48f841.txt index 16377236e8d..8143a485259 100644 --- a/.riot/requirements/b48f841.txt +++ b/.riot/requirements/b48f841.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/b48f841.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 mako==1.1.6 -markupsafe==2.1.5 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/e8d8aa5.txt b/.riot/requirements/e8d8aa5.txt index 32eaaf1e0a5..54a8f705f5c 100644 --- a/.riot/requirements/e8d8aa5.txt +++ b/.riot/requirements/e8d8aa5.txt @@ -4,18 +4,18 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/e8d8aa5.in # -attrs==24.2.0 -coverage[toml]==7.6.1 +attrs==24.3.0 +coverage[toml]==7.6.9 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.5 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 +pytest==8.3.4 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-randomly==3.16.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/eda6d79.txt b/.riot/requirements/eda6d79.txt index fd1ab1bf1d4..1cd10ab27f3 100644 --- a/.riot/requirements/eda6d79.txt +++ b/.riot/requirements/eda6d79.txt @@ -4,7 +4,7 @@ # # pip-compile --allow-unsafe --no-annotate .riot/requirements/eda6d79.in # -attrs==24.2.0 +attrs==24.3.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 hypothesis==6.45.0 @@ -14,12 +14,12 @@ mako==1.1.6 markupsafe==2.1.5 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pytest==8.3.3 +pytest==8.3.4 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 zipp==3.20.2 From 812af66c60f1845e6ad8ee946d854255023e1010 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:30:55 -0500 Subject: [PATCH 366/372] chore(docs): add vertexai, anthropic to integrations docs (#11840) Adds vertexAI and anthropic as items on the integrations docs page (including supported versions). ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/index.rst | 4 ++++ docs/integrations.rst | 9 ++++++++- docs/spelling_wordlist.txt | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 2435da88217..3d19a4fbf14 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -52,6 +52,8 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`algoliasearch` | >= 2.5.0 | Yes | +--------------------------------------------------+---------------+----------------+ +| :ref:`anthropic` | >= 0.28.0 | Yes | ++--------------------------------------------------+---------------+----------------+ | :ref:`aredis` | \* | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`asgi` | >= 3.0 | No | @@ -172,6 +174,8 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`urllib3` | >= 1.25.8 | No | +--------------------------------------------------+---------------+----------------+ +| :ref:`vertexai` | >= 1.71.1 | Yes | ++--------------------------------------------------+---------------+----------------+ | :ref:`vertica` | >= 0.6 | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`wsgi` | \* | No | diff --git a/docs/integrations.rst b/docs/integrations.rst index 04a94007626..62e096aa668 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -55,6 +55,13 @@ aiohttp_jinja2 .. automodule:: ddtrace.contrib.aiohttp_jinja2 +.. _anthropic: + +anthropic +^^^^^^^^^^^^^^ +.. automodule:: ddtrace.contrib.anthropic + + .. _asyncio: asyncio @@ -481,7 +488,7 @@ urllib3 .. _vertexai: vertexai -^^^^^^^^^^^^^^^^^^^ +^^^^^^^^ .. automodule:: ddtrace.contrib.vertexai diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 6bfa3f90bc1..c9cc13a5a9e 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -9,6 +9,8 @@ aioredis algolia algoliasearch analytics +anthropic +Anthropic AnyCallable api app From 6613185ccbb73aa3a982ac4b8e7659875a3fbad2 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Thu, 2 Jan 2025 10:56:05 -0500 Subject: [PATCH 367/372] fix: add schematize service name for django caching (#11843) Currently, we treat django caching as a separate service. This is a quick fix so that behavior aligns with our public docs; inferred services connected to django should be directly connected to the third-party service not django. The service name should adhere to the name rule from `schematize_service_name` rather than `django`. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Munir Abdinur Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- ddtrace/contrib/internal/django/patch.py | 2 +- ...ervice-name-schematization-bef19b44b7414016.yaml | 4 ++++ tests/contrib/django/test_django.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/django-cache-service-name-schematization-bef19b44b7414016.yaml diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index c40f49b627f..d4b14487e39 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -215,7 +215,7 @@ def traced_cache(django, pin, func, instance, args, kwargs): "django.cache", span_name="django.cache", span_type=SpanTypes.CACHE, - service=config.django.cache_service_name, + service=schematize_service_name(config.django.cache_service_name), resource=utils.resource_from_cache_prefix(func_name(func), instance), tags=tags, pin=pin, diff --git a/releasenotes/notes/django-cache-service-name-schematization-bef19b44b7414016.yaml b/releasenotes/notes/django-cache-service-name-schematization-bef19b44b7414016.yaml new file mode 100644 index 00000000000..0fbdb9993a5 --- /dev/null +++ b/releasenotes/notes/django-cache-service-name-schematization-bef19b44b7414016.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + tracing(django): Fixes issue where django cache is represented as a django service rather than the third party service. \ No newline at end of file diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index cf44ed6bcdd..1bd223539c9 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -30,6 +30,7 @@ from ddtrace.ext import http from ddtrace.ext import user from ddtrace.internal.compat import ensure_text +from ddtrace.internal.schema import schematize_service_name from ddtrace.propagation._utils import get_wsgi_header from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY @@ -769,6 +770,18 @@ def test_cache_get(test_spans): assert_dict_issuperset(span.get_tags(), expected_meta) +def test_cache_service_schematization(test_spans): + cache = django.core.cache.caches["default"] + + with override_config("django", dict(cache_service_name="test-cache-service")): + cache.get("missing_key") + spans = test_spans.get_spans() + assert spans + span = spans[0] + expected_service_name = schematize_service_name(config.django.cache_service_name) + assert span.service == expected_service_name + + def test_cache_get_rowcount_existing_key(test_spans): # get the default cache cache = django.core.cache.caches["default"] From e1eccbd9e33dd6e7636b739578d5f4ed5ed30819 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 2 Jan 2025 13:28:50 -0500 Subject: [PATCH 368/372] chore(integrations): remove required_modules from some integrations (#11719) Since ddtrace [v1.9.0](https://github.com/DataDog/dd-trace-py/commit/856fa23317e82872690ad6a691a3d6fd239c3454) modules are only patched after import. There is no need to check whether a module exists in `ddtrace.contrib.integration_name.__init__`. If a library is not installed, it won't be patched. `require_modules` should only be used when a ddtrace integration requires additional libraries (for example is web_integration can only be enabled if web_integration_driver is also installed). Right now there is no integrations that have such a dependency. With this change objects exposed in `ddtrace.contrib..__all__` will always be available. Accessing these objects will not raise an attributeerror and crash an application: [ex](https://github.com/DataDog/dd-trace-py/issues/11603). ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/aiobotocore/__init__.py | 23 ++++---- ddtrace/contrib/aiohttp/__init__.py | 27 ++++----- ddtrace/contrib/aiohttp_jinja2/__init__.py | 23 ++++---- ddtrace/contrib/aiomysql/__init__.py | 22 +++---- ddtrace/contrib/aiopg/__init__.py | 21 +++---- ddtrace/contrib/aioredis/__init__.py | 23 +++----- ddtrace/contrib/algoliasearch/__init__.py | 22 +++---- ddtrace/contrib/anthropic/__init__.py | 23 ++++---- ddtrace/contrib/aredis/__init__.py | 20 +++---- ddtrace/contrib/asgi/__init__.py | 16 ++---- ddtrace/contrib/asyncio/__init__.py | 35 +++++------- ddtrace/contrib/asyncpg/__init__.py | 23 ++++---- ddtrace/contrib/avro/__init__.py | 14 ++--- ddtrace/contrib/boto/__init__.py | 20 +++---- ddtrace/contrib/botocore/__init__.py | 23 +++----- ddtrace/contrib/bottle/__init__.py | 22 +++---- ddtrace/contrib/cassandra/__init__.py | 23 ++++---- ddtrace/contrib/celery/__init__.py | 29 +++++----- ddtrace/contrib/cherrypy/__init__.py | 11 +--- ddtrace/contrib/consul/__init__.py | 24 ++++---- ddtrace/contrib/coverage/__init__.py | 29 ++++------ ddtrace/contrib/django/__init__.py | 27 ++++----- ddtrace/contrib/dogpile_cache/__init__.py | 25 ++++---- ddtrace/contrib/dramatiq/__init__.py | 25 ++++---- ddtrace/contrib/falcon/__init__.py | 23 ++++---- ddtrace/contrib/fastapi/__init__.py | 25 ++++---- ddtrace/contrib/flask/__init__.py | 22 +++---- ddtrace/contrib/flask_cache/__init__.py | 17 ++---- ddtrace/contrib/freezegun/__init__.py | 16 ++---- ddtrace/contrib/futures/__init__.py | 33 +++++------ ddtrace/contrib/gevent/__init__.py | 29 +++++----- .../contrib/google_generativeai/__init__.py | 12 ++-- ddtrace/contrib/graphql/__init__.py | 25 ++++---- ddtrace/contrib/grpc/__init__.py | 25 ++++---- ddtrace/contrib/httplib/__init__.py | 18 +++--- ddtrace/contrib/httpx/__init__.py | 25 ++++---- ddtrace/contrib/jinja2/__init__.py | 24 ++++---- ddtrace/contrib/kafka/__init__.py | 25 ++++---- ddtrace/contrib/langchain/__init__.py | 24 ++++---- ddtrace/contrib/logbook/__init__.py | 24 ++++---- ddtrace/contrib/logging/__init__.py | 24 ++++---- ddtrace/contrib/loguru/__init__.py | 24 ++++---- ddtrace/contrib/mako/__init__.py | 25 ++++---- ddtrace/contrib/mariadb/__init__.py | 25 ++++---- ddtrace/contrib/molten/__init__.py | 25 ++++---- ddtrace/contrib/mongoengine/__init__.py | 22 +++---- ddtrace/contrib/mysql/__init__.py | 25 ++++---- ddtrace/contrib/mysqldb/__init__.py | 23 ++++---- ddtrace/contrib/openai/__init__.py | 25 ++++---- ddtrace/contrib/protobuf/__init__.py | 16 ++---- ddtrace/contrib/pylibmc/__init__.py | 24 ++++---- ddtrace/contrib/pymemcache/__init__.py | 25 ++++---- ddtrace/contrib/pymongo/__init__.py | 23 ++++---- ddtrace/contrib/pymysql/__init__.py | 22 +++---- ddtrace/contrib/pynamodb/__init__.py | 23 +++----- ddtrace/contrib/pyodbc/__init__.py | 23 ++++---- ddtrace/contrib/pyramid/__init__.py | 28 ++++----- ddtrace/contrib/redis/__init__.py | 22 +++---- ddtrace/contrib/rediscluster/__init__.py | 22 +++---- ddtrace/contrib/requests/__init__.py | 27 ++++----- ddtrace/contrib/sanic/__init__.py | 25 ++++---- ddtrace/contrib/selenium/__init__.py | 16 ++---- ddtrace/contrib/snowflake/__init__.py | 25 ++++---- ddtrace/contrib/sqlalchemy/__init__.py | 27 ++++----- ddtrace/contrib/sqlite3/__init__.py | 23 ++++---- ddtrace/contrib/starlette/__init__.py | 25 ++++---- ddtrace/contrib/structlog/__init__.py | 24 ++++---- ddtrace/contrib/subprocess/__init__.py | 24 ++++---- ddtrace/contrib/tornado/__init__.py | 57 +++++++++---------- ddtrace/contrib/unittest/__init__.py | 8 +-- ddtrace/contrib/urllib/__init__.py | 25 ++++---- ddtrace/contrib/urllib3/__init__.py | 25 ++++---- ddtrace/contrib/vertexai/__init__.py | 13 ++--- ddtrace/contrib/vertica/__init__.py | 24 ++++---- ddtrace/contrib/webbrowser/__init__.py | 25 ++++---- ddtrace/contrib/yaaredis/__init__.py | 22 +++---- docs/conf.py | 6 ++ templates/integration/__init__.py | 13 ++--- 78 files changed, 753 insertions(+), 1044 deletions(-) diff --git a/ddtrace/contrib/aiobotocore/__init__.py b/ddtrace/contrib/aiobotocore/__init__.py index 8869c4647d1..47f16f51796 100644 --- a/ddtrace/contrib/aiobotocore/__init__.py +++ b/ddtrace/contrib/aiobotocore/__init__.py @@ -25,22 +25,19 @@ Default: ``False`` """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["aiobotocore.client"] +# Required to allow users to import from `ddtrace.contrib.aiobotocore.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aiobotocore.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.aiobotocore.patch import get_version - from ddtrace.contrib.internal.aiobotocore.patch import patch +# Expose public methods +from ddtrace.contrib.internal.aiobotocore.patch import get_version +from ddtrace.contrib.internal.aiobotocore.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/aiohttp/__init__.py b/ddtrace/contrib/aiohttp/__init__.py index 7ff17c21ec0..2bd88e734de 100644 --- a/ddtrace/contrib/aiohttp/__init__.py +++ b/ddtrace/contrib/aiohttp/__init__.py @@ -84,24 +84,21 @@ async def home_handler(request): :ref:`All HTTP tags ` are supported for this integration. """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["aiohttp"] +# Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.aiohttp.middlewares import trace_app - from ddtrace.contrib.internal.aiohttp.patch import get_version +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.aiohttp.middlewares import trace_app +from ddtrace.contrib.internal.aiohttp.patch import get_version - # Expose public methods - from ddtrace.contrib.internal.aiohttp.patch import patch - from ddtrace.contrib.internal.aiohttp.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.aiohttp.patch import patch +from ddtrace.contrib.internal.aiohttp.patch import unpatch - __all__ = ["patch", "unpatch", "trace_app", "get_version"] + +__all__ = ["patch", "unpatch", "trace_app", "get_version"] diff --git a/ddtrace/contrib/aiohttp_jinja2/__init__.py b/ddtrace/contrib/aiohttp_jinja2/__init__.py index fe268b6dc54..ef9c76f3da8 100644 --- a/ddtrace/contrib/aiohttp_jinja2/__init__.py +++ b/ddtrace/contrib/aiohttp_jinja2/__init__.py @@ -13,22 +13,19 @@ from ddtrace import patch patch(aiohttp_jinja2=True) """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["aiohttp_jinja2"] +# Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.aiohttp_jinja2.patch import get_version - from ddtrace.contrib.internal.aiohttp_jinja2.patch import patch - from ddtrace.contrib.internal.aiohttp_jinja2.patch import unpatch +from ddtrace.contrib.internal.aiohttp_jinja2.patch import get_version +from ddtrace.contrib.internal.aiohttp_jinja2.patch import patch +from ddtrace.contrib.internal.aiohttp_jinja2.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/aiomysql/__init__.py b/ddtrace/contrib/aiomysql/__init__.py index 485eb690fed..98f78d3f3ab 100644 --- a/ddtrace/contrib/aiomysql/__init__.py +++ b/ddtrace/contrib/aiomysql/__init__.py @@ -36,22 +36,18 @@ await cur.execute("SELECT 6*7 AS the_answer;") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly +import warnings as _w -required_modules = ["aiomysql"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.aiomysql.patch import get_version +from ddtrace.contrib.internal.aiomysql.patch import patch +from ddtrace.contrib.internal.aiomysql.patch import unpatch - from ddtrace.contrib.internal.aiomysql.patch import get_version - from ddtrace.contrib.internal.aiomysql.patch import patch - from ddtrace.contrib.internal.aiomysql.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/aiopg/__init__.py b/ddtrace/contrib/aiopg/__init__.py index 481f9133e57..11a572d12ed 100644 --- a/ddtrace/contrib/aiopg/__init__.py +++ b/ddtrace/contrib/aiopg/__init__.py @@ -15,21 +15,18 @@ # Use a pin to specify metadata related to this connection Pin.override(db, service='postgres-users') """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["aiopg"] +# Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.aiopg.patch import get_version - from ddtrace.contrib.internal.aiopg.patch import patch +from ddtrace.contrib.internal.aiopg.patch import get_version +from ddtrace.contrib.internal.aiopg.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/aioredis/__init__.py b/ddtrace/contrib/aioredis/__init__.py index 32449af6b6f..2bc3669a340 100644 --- a/ddtrace/contrib/aioredis/__init__.py +++ b/ddtrace/contrib/aioredis/__init__.py @@ -69,22 +69,17 @@ myaioredis = aioredis.Aioredis() Pin.override(myaioredis, service="myaioredis") """ -from ddtrace.internal.utils.importlib import require_modules # noqa:E402 +import warnings as _w # noqa: E402 -required_modules = ["aioredis"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + # Required to allow users to import from `ddtrace.contrib.aioredis.patch` directly + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aioredis.patch` directly - import warnings as _w +from ddtrace.contrib.internal.aioredis.patch import get_version # noqa: E402 +from ddtrace.contrib.internal.aioredis.patch import patch # noqa: E402 +from ddtrace.contrib.internal.aioredis.patch import unpatch # noqa: E402 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.aioredis.patch import get_version - from ddtrace.contrib.internal.aioredis.patch import patch - from ddtrace.contrib.internal.aioredis.patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/algoliasearch/__init__.py b/ddtrace/contrib/algoliasearch/__init__.py index 4b172bca67a..b594d6238cc 100644 --- a/ddtrace/contrib/algoliasearch/__init__.py +++ b/ddtrace/contrib/algoliasearch/__init__.py @@ -22,22 +22,18 @@ .. __: https://www.algolia.com """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.algoliasearch.patch` directly +import warnings as _w -required_modules = ["algoliasearch", "algoliasearch.version"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.algoliasearch.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.algoliasearch.patch import get_version +from ddtrace.contrib.internal.algoliasearch.patch import patch +from ddtrace.contrib.internal.algoliasearch.patch import unpatch - from ddtrace.contrib.internal.algoliasearch.patch import get_version - from ddtrace.contrib.internal.algoliasearch.patch import patch - from ddtrace.contrib.internal.algoliasearch.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/anthropic/__init__.py b/ddtrace/contrib/anthropic/__init__.py index 7a55e6947c9..c43ee4bb43c 100644 --- a/ddtrace/contrib/anthropic/__init__.py +++ b/ddtrace/contrib/anthropic/__init__.py @@ -80,22 +80,19 @@ Pin.override(anthropic, service="my-anthropic-service") """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["anthropic"] +# Required to allow users to import from `ddtrace.contrib.anthropic.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.anthropic.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.anthropic.patch import get_version - from ddtrace.contrib.internal.anthropic.patch import patch - from ddtrace.contrib.internal.anthropic.patch import unpatch +from ddtrace.contrib.internal.anthropic.patch import get_version +from ddtrace.contrib.internal.anthropic.patch import patch +from ddtrace.contrib.internal.anthropic.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/aredis/__init__.py b/ddtrace/contrib/aredis/__init__.py index 17c1408caa5..1af4c5db664 100644 --- a/ddtrace/contrib/aredis/__init__.py +++ b/ddtrace/contrib/aredis/__init__.py @@ -66,21 +66,17 @@ async def example(): await client.get("my-key") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.aredis.patch` directly +import warnings as _w -required_modules = ["aredis", "aredis.client"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.aredis.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.aredis.patch import get_version +from ddtrace.contrib.internal.aredis.patch import patch - from ddtrace.contrib.internal.aredis.patch import get_version - from ddtrace.contrib.internal.aredis.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/asgi/__init__.py b/ddtrace/contrib/asgi/__init__.py index 0bfcd1ade5b..8c30f4e609e 100644 --- a/ddtrace/contrib/asgi/__init__.py +++ b/ddtrace/contrib/asgi/__init__.py @@ -55,18 +55,10 @@ def handle_request(scope, send): .. __: https://asgi.readthedocs.io/ """ -from ddtrace.internal.utils.importlib import require_modules +from ddtrace.contrib.internal.asgi.middleware import TraceMiddleware +from ddtrace.contrib.internal.asgi.middleware import get_version +from ddtrace.contrib.internal.asgi.middleware import span_from_scope -required_modules = [] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.asgi.patch` directly - from . import middleware as _ # noqa: F401, I001 - - from ddtrace.contrib.internal.asgi.middleware import TraceMiddleware - from ddtrace.contrib.internal.asgi.middleware import get_version - from ddtrace.contrib.internal.asgi.middleware import span_from_scope - - __all__ = ["TraceMiddleware", "span_from_scope", "get_version"] +__all__ = ["TraceMiddleware", "span_from_scope", "get_version"] diff --git a/ddtrace/contrib/asyncio/__init__.py b/ddtrace/contrib/asyncio/__init__.py index 5ff3a69adee..205935222fb 100644 --- a/ddtrace/contrib/asyncio/__init__.py +++ b/ddtrace/contrib/asyncio/__init__.py @@ -2,29 +2,24 @@ This integration provides context management for tracing the execution flow of concurrent execution of ``asyncio.Task``. """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.asyncio.patch` directly +# Expose public methods +import warnings as _w # noqa:E402 -required_modules = ["asyncio"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +from ddtrace._trace.provider import DefaultContextProvider +from ddtrace.contrib.internal.asyncio.helpers import ensure_future +from ddtrace.contrib.internal.asyncio.helpers import run_in_executor +from ddtrace.contrib.internal.asyncio.helpers import set_call_context +from ddtrace.contrib.internal.asyncio.patch import get_version +from ddtrace.contrib.internal.asyncio.patch import patch +from ddtrace.contrib.internal.asyncio.patch import unpatch # noqa: F401 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - from ddtrace._trace.provider import DefaultContextProvider - context_provider = DefaultContextProvider() +context_provider = DefaultContextProvider() - # Required to allow users to import from `ddtrace.contrib.asyncio.patch` directly - # Expose public methods - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.asyncio.helpers import ensure_future - from ddtrace.contrib.internal.asyncio.helpers import run_in_executor - from ddtrace.contrib.internal.asyncio.helpers import set_call_context - from ddtrace.contrib.internal.asyncio.patch import get_version - from ddtrace.contrib.internal.asyncio.patch import patch - from ddtrace.contrib.internal.asyncio.patch import unpatch # noqa: F401 - - __all__ = ["context_provider", "set_call_context", "ensure_future", "run_in_executor", "patch", "get_version"] +__all__ = ["context_provider", "set_call_context", "ensure_future", "run_in_executor", "patch", "get_version"] diff --git a/ddtrace/contrib/asyncpg/__init__.py b/ddtrace/contrib/asyncpg/__init__.py index 21cc9b613a9..c8e56511469 100644 --- a/ddtrace/contrib/asyncpg/__init__.py +++ b/ddtrace/contrib/asyncpg/__init__.py @@ -43,22 +43,19 @@ conn = asyncpg.connect("postgres://localhost:5432") Pin.override(conn, service="custom-service") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["asyncpg"] +# Required to allow users to import from `ddtrace.contrib.asyncpg.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.asyncpg.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.asyncpg.patch import get_version - from ddtrace.contrib.internal.asyncpg.patch import patch - from ddtrace.contrib.internal.asyncpg.patch import unpatch +from ddtrace.contrib.internal.asyncpg.patch import get_version +from ddtrace.contrib.internal.asyncpg.patch import patch +from ddtrace.contrib.internal.asyncpg.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/avro/__init__.py b/ddtrace/contrib/avro/__init__.py index 6ca7f3c9847..328704bbfea 100644 --- a/ddtrace/contrib/avro/__init__.py +++ b/ddtrace/contrib/avro/__init__.py @@ -15,15 +15,9 @@ ~~~~~~~~~~~~~ """ -from ...internal.utils.importlib import require_modules +# Expose public methods +from ..internal.avro.patch import get_version +from ..internal.avro.patch import patch -required_modules = ["avro"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Expose public methods - from ..internal.avro.patch import get_version - from ..internal.avro.patch import patch - - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/boto/__init__.py b/ddtrace/contrib/boto/__init__.py index e17db53e5ae..6b967b28b2b 100644 --- a/ddtrace/contrib/boto/__init__.py +++ b/ddtrace/contrib/boto/__init__.py @@ -28,21 +28,17 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.boto.patch` directly +import warnings as _w -required_modules = ["boto.connection"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.boto.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.boto.patch import get_version +from ddtrace.contrib.internal.boto.patch import patch - from ddtrace.contrib.internal.boto.patch import get_version - from ddtrace.contrib.internal.boto.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/botocore/__init__.py b/ddtrace/contrib/botocore/__init__.py index 143ef70b9d7..60b01d39839 100644 --- a/ddtrace/contrib/botocore/__init__.py +++ b/ddtrace/contrib/botocore/__init__.py @@ -153,22 +153,17 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.botocore.patch` directly +import warnings as _w -required_modules = ["botocore.client"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.botocore.patch` directly - import warnings as _w +from ddtrace.contrib.internal.botocore.patch import get_version +from ddtrace.contrib.internal.botocore.patch import patch +from ddtrace.contrib.internal.botocore.patch import patch_submodules - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.botocore.patch import get_version - from ddtrace.contrib.internal.botocore.patch import patch - from ddtrace.contrib.internal.botocore.patch import patch_submodules - - __all__ = ["patch", "patch_submodules", "get_version"] +__all__ = ["patch", "patch_submodules", "get_version"] diff --git a/ddtrace/contrib/bottle/__init__.py b/ddtrace/contrib/bottle/__init__.py index 1391f1cd667..448c709b952 100644 --- a/ddtrace/contrib/bottle/__init__.py +++ b/ddtrace/contrib/bottle/__init__.py @@ -33,22 +33,18 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.bottle.patch` directly +import warnings as _w -required_modules = ["bottle"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.bottle.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.bottle.patch import get_version +from ddtrace.contrib.internal.bottle.patch import patch +from ddtrace.contrib.internal.bottle.trace import TracePlugin - from ddtrace.contrib.internal.bottle.patch import get_version - from ddtrace.contrib.internal.bottle.patch import patch - from ddtrace.contrib.internal.bottle.trace import TracePlugin - __all__ = ["TracePlugin", "patch", "get_version"] +__all__ = ["TracePlugin", "patch", "get_version"] diff --git a/ddtrace/contrib/cassandra/__init__.py b/ddtrace/contrib/cassandra/__init__.py index 9c3c1d7de6a..1d0b6ad0afd 100644 --- a/ddtrace/contrib/cassandra/__init__.py +++ b/ddtrace/contrib/cassandra/__init__.py @@ -21,22 +21,19 @@ session = cluster.connect("my_keyspace") session.execute("select id from my_table limit 10;") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["cassandra.cluster"] +# Required to allow users to import from `ddtrace.contrib.cassandra.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.cassandra.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.cassandra.patch import patch - from ddtrace.contrib.internal.cassandra.session import get_version +# Expose public methods +from ddtrace.contrib.internal.cassandra.patch import patch +from ddtrace.contrib.internal.cassandra.session import get_version - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/celery/__init__.py b/ddtrace/contrib/celery/__init__.py index 7b63089e206..83ad7a870a3 100644 --- a/ddtrace/contrib/celery/__init__.py +++ b/ddtrace/contrib/celery/__init__.py @@ -51,25 +51,22 @@ def run(self): Default: ``'celery-worker'`` """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["celery"] +# Required to allow users to import from `ddtrace.contrib.celery.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.celery.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.celery.app import patch_app - from ddtrace.contrib.internal.celery.app import unpatch_app - from ddtrace.contrib.internal.celery.patch import get_version - from ddtrace.contrib.internal.celery.patch import patch - from ddtrace.contrib.internal.celery.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.celery.app import patch_app +from ddtrace.contrib.internal.celery.app import unpatch_app +from ddtrace.contrib.internal.celery.patch import get_version +from ddtrace.contrib.internal.celery.patch import patch +from ddtrace.contrib.internal.celery.patch import unpatch - __all__ = ["patch", "patch_app", "unpatch", "unpatch_app", "get_version"] + +__all__ = ["patch", "patch_app", "unpatch", "unpatch_app", "get_version"] diff --git a/ddtrace/contrib/cherrypy/__init__.py b/ddtrace/contrib/cherrypy/__init__.py index ff072fa426f..efd1210db6c 100644 --- a/ddtrace/contrib/cherrypy/__init__.py +++ b/ddtrace/contrib/cherrypy/__init__.py @@ -53,14 +53,9 @@ def index(self): cherrypy.quickstart(HelloWorld()) """ -from ddtrace.internal.utils.importlib import require_modules +from ddtrace.contrib.internal.cherrypy.middleware import TraceMiddleware +from ddtrace.contrib.internal.cherrypy.middleware import get_version -required_modules = ["cherrypy"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - from ddtrace.contrib.internal.cherrypy.middleware import TraceMiddleware - from ddtrace.contrib.internal.cherrypy.middleware import get_version - - __all__ = ["TraceMiddleware", "get_version"] +__all__ = ["TraceMiddleware", "get_version"] diff --git a/ddtrace/contrib/consul/__init__.py b/ddtrace/contrib/consul/__init__.py index 8b7498c80d3..a6317d0bce0 100644 --- a/ddtrace/contrib/consul/__init__.py +++ b/ddtrace/contrib/consul/__init__.py @@ -19,23 +19,19 @@ Pin.override(client, service='consul-kv') """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.consul.patch` directly +import warnings as _w -required_modules = ["consul"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.consul.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.consul.patch import get_version +from ddtrace.contrib.internal.consul.patch import patch +from ddtrace.contrib.internal.consul.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.consul.patch import get_version - from ddtrace.contrib.internal.consul.patch import patch - from ddtrace.contrib.internal.consul.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/coverage/__init__.py b/ddtrace/contrib/coverage/__init__.py index b32d27ebff4..6ae090e3fe0 100644 --- a/ddtrace/contrib/coverage/__init__.py +++ b/ddtrace/contrib/coverage/__init__.py @@ -15,26 +15,21 @@ Note: Coverage.py instrumentation is only enabled if `pytest` or `unittest` instrumentation is enabled. """ -from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.importlib import require_modules - +# Required to allow users to import from `ddtrace.contrib.internal.coverage.patch` directly +import warnings as _w # noqa:E402 -required_modules = ["coverage"] -log = get_logger(__name__) +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.internal.coverage.patch` directly - import warnings as _w +# Expose public methods +from ddtrace.contrib.internal.coverage.patch import get_version +from ddtrace.contrib.internal.coverage.patch import patch +from ddtrace.contrib.internal.coverage.patch import unpatch +from ddtrace.internal.logger import get_logger - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.coverage.patch import get_version - from ddtrace.contrib.internal.coverage.patch import patch - from ddtrace.contrib.internal.coverage.patch import unpatch +log = get_logger(__name__) - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/django/__init__.py b/ddtrace/contrib/django/__init__.py index 55a867ed560..2eeea57b2c6 100644 --- a/ddtrace/contrib/django/__init__.py +++ b/ddtrace/contrib/django/__init__.py @@ -201,25 +201,20 @@ .. __: https://www.djangoproject.com/ """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.django.patch` directly +import warnings as _w -required_modules = ["django"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.django.patch` directly - import warnings as _w +# Expose public methods +from ddtrace.contrib.internal.django.patch import get_version +from ddtrace.contrib.internal.django.patch import patch +from ddtrace.contrib.internal.django.patch import patch as _patch +from ddtrace.contrib.internal.django.patch import unpatch - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.django.patch import get_version - from ddtrace.contrib.internal.django.patch import patch - from ddtrace.contrib.internal.django.patch import patch as _patch - from ddtrace.contrib.internal.django.patch import unpatch - - __all__ = ["patch", "unpatch", "_patch", "get_version"] +__all__ = ["patch", "unpatch", "_patch", "get_version"] diff --git a/ddtrace/contrib/dogpile_cache/__init__.py b/ddtrace/contrib/dogpile_cache/__init__.py index 6e796e93320..63898aa4a5a 100644 --- a/ddtrace/contrib/dogpile_cache/__init__.py +++ b/ddtrace/contrib/dogpile_cache/__init__.py @@ -36,23 +36,20 @@ def hello(name): .. __: https://dogpilecache.sqlalchemy.org/ """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["dogpile.cache"] +# Required to allow users to import from `ddtrace.contrib.dogpile_cache.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.dogpile_cache.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.dogpile_cache.patch import get_version - from ddtrace.contrib.internal.dogpile_cache.patch import patch - from ddtrace.contrib.internal.dogpile_cache.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.dogpile_cache.patch import get_version +from ddtrace.contrib.internal.dogpile_cache.patch import patch +from ddtrace.contrib.internal.dogpile_cache.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/dramatiq/__init__.py b/ddtrace/contrib/dramatiq/__init__.py index ebf2c355743..2e5f37c4cba 100644 --- a/ddtrace/contrib/dramatiq/__init__.py +++ b/ddtrace/contrib/dramatiq/__init__.py @@ -28,23 +28,20 @@ def my_other_task(content): ddtrace-run python app.py """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["dramatiq"] +# Required to allow users to import from `ddtrace.contrib.dramatiq.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.dramatiq.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.dramatiq.patch import get_version - from ddtrace.contrib.internal.dramatiq.patch import patch - from ddtrace.contrib.internal.dramatiq.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.dramatiq.patch import get_version +from ddtrace.contrib.internal.dramatiq.patch import patch +from ddtrace.contrib.internal.dramatiq.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/falcon/__init__.py b/ddtrace/contrib/falcon/__init__.py index e8d2038ccc1..c54b58dbd9c 100644 --- a/ddtrace/contrib/falcon/__init__.py +++ b/ddtrace/contrib/falcon/__init__.py @@ -44,21 +44,18 @@ def on_falcon_request(span, request, response): :ref:`Headers tracing ` is supported for this integration. """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["falcon"] +# Required to allow users to import from `ddtrace.contrib.falcon.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.falcon.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.falcon.middleware import TraceMiddleware - from ddtrace.contrib.internal.falcon.patch import get_version - from ddtrace.contrib.internal.falcon.patch import patch +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.falcon.middleware import TraceMiddleware +from ddtrace.contrib.internal.falcon.patch import get_version +from ddtrace.contrib.internal.falcon.patch import patch - __all__ = ["TraceMiddleware", "patch", "get_version"] + +__all__ = ["TraceMiddleware", "patch", "get_version"] diff --git a/ddtrace/contrib/fastapi/__init__.py b/ddtrace/contrib/fastapi/__init__.py index 9931a2d3ea6..df989abf766 100644 --- a/ddtrace/contrib/fastapi/__init__.py +++ b/ddtrace/contrib/fastapi/__init__.py @@ -50,23 +50,20 @@ config.fastapi['request_span_name'] = 'custom-request-span-name' """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["fastapi"] +# Required to allow users to import from `ddtrace.contrib.fastapi.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.fastapi.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.fastapi.patch import get_version - from ddtrace.contrib.internal.fastapi.patch import patch - from ddtrace.contrib.internal.fastapi.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.fastapi.patch import get_version +from ddtrace.contrib.internal.fastapi.patch import patch +from ddtrace.contrib.internal.fastapi.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/flask/__init__.py b/ddtrace/contrib/flask/__init__.py index 47226eed5c6..22e90ffb604 100644 --- a/ddtrace/contrib/flask/__init__.py +++ b/ddtrace/contrib/flask/__init__.py @@ -96,21 +96,17 @@ def index(): """ -from ddtrace.internal.utils.importlib import require_modules +# DEV: We do this so we can `@mock.patch('ddtrace.contrib.flask._patch.')` in tests +import warnings as _w -required_modules = ["flask"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # DEV: We do this so we can `@mock.patch('ddtrace.contrib.flask._patch.')` in tests - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +from ddtrace.contrib.internal.flask.patch import get_version +from ddtrace.contrib.internal.flask.patch import patch +from ddtrace.contrib.internal.flask.patch import unpatch - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.flask.patch import get_version - from ddtrace.contrib.internal.flask.patch import patch - from ddtrace.contrib.internal.flask.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/flask_cache/__init__.py b/ddtrace/contrib/flask_cache/__init__.py index 1919f55efb5..f11b006f723 100644 --- a/ddtrace/contrib/flask_cache/__init__.py +++ b/ddtrace/contrib/flask_cache/__init__.py @@ -44,18 +44,9 @@ def counter(): """ -from ddtrace.internal.utils.importlib import require_modules +# Expose public methods +from ddtrace.contrib.internal.flask_cache.tracers import get_traced_cache +from ddtrace.contrib.internal.flask_cache.tracers import get_version -required_modules = ["flask_cache", "flask_caching"] - -with require_modules(required_modules) as missing_modules: - if len(missing_modules) < len(required_modules): - # Required to allow users to import from `ddtrace.contrib.aiohttp.patch` directly - from . import tracers as _ # noqa: F401, I001 - - # Expose public methods - from ddtrace.contrib.internal.flask_cache.tracers import get_traced_cache - from ddtrace.contrib.internal.flask_cache.tracers import get_version - - __all__ = ["get_traced_cache", "get_version"] +__all__ = ["get_traced_cache", "get_version"] diff --git a/ddtrace/contrib/freezegun/__init__.py b/ddtrace/contrib/freezegun/__init__.py index 7e2df3c557c..8c072c74d5a 100644 --- a/ddtrace/contrib/freezegun/__init__.py +++ b/ddtrace/contrib/freezegun/__init__.py @@ -13,16 +13,10 @@ The freezegun integration is not configurable, but may be disabled using DD_PATCH_MODULES=freezegun:false . """ -from ...internal.utils.importlib import require_modules +# Expose public methods +from ..internal.freezegun.patch import get_version +from ..internal.freezegun.patch import patch +from ..internal.freezegun.patch import unpatch -required_modules = ["freezegun"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Expose public methods - from ..internal.freezegun.patch import get_version - from ..internal.freezegun.patch import patch - from ..internal.freezegun.patch import unpatch - - __all__ = ["get_version", "patch", "unpatch"] +__all__ = ["get_version", "patch", "unpatch"] diff --git a/ddtrace/contrib/futures/__init__.py b/ddtrace/contrib/futures/__init__.py index c589912c07a..1ba8741ebe9 100644 --- a/ddtrace/contrib/futures/__init__.py +++ b/ddtrace/contrib/futures/__init__.py @@ -16,27 +16,24 @@ from ddtrace import patch patch(futures=True) """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["concurrent.futures"] +# Required to allow users to import from `ddtrace.contrib.futures.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.futures.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.futures.patch import get_version - from ddtrace.contrib.internal.futures.patch import patch - from ddtrace.contrib.internal.futures.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.futures.patch import get_version +from ddtrace.contrib.internal.futures.patch import patch +from ddtrace.contrib.internal.futures.patch import unpatch - __all__ = [ - "get_version", - "patch", - "unpatch", - ] + +__all__ = [ + "get_version", + "patch", + "unpatch", +] diff --git a/ddtrace/contrib/gevent/__init__.py b/ddtrace/contrib/gevent/__init__.py index 1b8e2811ef5..fb051e5f88b 100644 --- a/ddtrace/contrib/gevent/__init__.py +++ b/ddtrace/contrib/gevent/__init__.py @@ -36,26 +36,23 @@ def worker_function(): with tracer.trace("greenlet.child_call") as child: ... """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.gevent.patch` directly +import warnings as _w -required_modules = ["gevent"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.gevent.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.gevent.patch import get_version - from ddtrace.contrib.internal.gevent.patch import patch - from ddtrace.contrib.internal.gevent.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.gevent.patch import get_version +from ddtrace.contrib.internal.gevent.patch import patch +from ddtrace.contrib.internal.gevent.patch import unpatch - from ...provider import DefaultContextProvider as _DefaultContextProvider +from ...provider import DefaultContextProvider as _DefaultContextProvider - context_provider = _DefaultContextProvider() - __all__ = ["patch", "unpatch", "context_provider", "get_version"] +context_provider = _DefaultContextProvider() + +__all__ = ["patch", "unpatch", "context_provider", "get_version"] diff --git a/ddtrace/contrib/google_generativeai/__init__.py b/ddtrace/contrib/google_generativeai/__init__.py index 03cb35c9eae..d63a1134ab2 100644 --- a/ddtrace/contrib/google_generativeai/__init__.py +++ b/ddtrace/contrib/google_generativeai/__init__.py @@ -77,15 +77,11 @@ Pin.override(genai, service="my-gemini-service") """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["google.generativeai"] +from ..internal.google_generativeai.patch import get_version +from ..internal.google_generativeai.patch import patch +from ..internal.google_generativeai.patch import unpatch -with require_modules(required_modules) as missing_modules: - if not missing_modules: - from ..internal.google_generativeai.patch import get_version - from ..internal.google_generativeai.patch import patch - from ..internal.google_generativeai.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/graphql/__init__.py b/ddtrace/contrib/graphql/__init__.py index 89350191ef1..5394f243533 100644 --- a/ddtrace/contrib/graphql/__init__.py +++ b/ddtrace/contrib/graphql/__init__.py @@ -44,23 +44,20 @@ Pin.override(graphql, service="mygraphql") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["graphql"] +# Required to allow users to import from `ddtrace.contrib.graphql.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.graphql.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.graphql.patch import get_version - from ddtrace.contrib.internal.graphql.patch import patch - from ddtrace.contrib.internal.graphql.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.graphql.patch import get_version +from ddtrace.contrib.internal.graphql.patch import patch +from ddtrace.contrib.internal.graphql.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/grpc/__init__.py b/ddtrace/contrib/grpc/__init__.py index c329762316c..ff5adb86aea 100644 --- a/ddtrace/contrib/grpc/__init__.py +++ b/ddtrace/contrib/grpc/__init__.py @@ -75,23 +75,18 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.grpc.patch` directly +import warnings as _w -required_modules = ["grpc"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.grpc.patch` directly - import warnings as _w +# Expose public methods +from ddtrace.contrib.internal.grpc.patch import get_version +from ddtrace.contrib.internal.grpc.patch import patch +from ddtrace.contrib.internal.grpc.patch import unpatch - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.grpc.patch import get_version - from ddtrace.contrib.internal.grpc.patch import patch - from ddtrace.contrib.internal.grpc.patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/httplib/__init__.py b/ddtrace/contrib/httplib/__init__.py index 509e80df270..948b885199a 100644 --- a/ddtrace/contrib/httplib/__init__.py +++ b/ddtrace/contrib/httplib/__init__.py @@ -58,22 +58,18 @@ :ref:`Headers tracing ` is supported for this integration. """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["http.client"] +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - from ddtrace.contrib.internal.httplib.patch import get_version - from ddtrace.contrib.internal.httplib.patch import patch - from ddtrace.contrib.internal.httplib.patch import unpatch +from ddtrace.contrib.internal.httplib.patch import get_version +from ddtrace.contrib.internal.httplib.patch import patch +from ddtrace.contrib.internal.httplib.patch import unpatch __all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/httpx/__init__.py b/ddtrace/contrib/httpx/__init__.py index 4575d39e7c6..118d0a738b5 100644 --- a/ddtrace/contrib/httpx/__init__.py +++ b/ddtrace/contrib/httpx/__init__.py @@ -77,23 +77,20 @@ .. __: https://www.python-httpx.org/ """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["httpx"] +# Required to allow users to import from `ddtrace.contrib.httpx.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.httpx.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.httpx.patch import get_version - from ddtrace.contrib.internal.httpx.patch import patch - from ddtrace.contrib.internal.httpx.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.httpx.patch import get_version +from ddtrace.contrib.internal.httpx.patch import patch +from ddtrace.contrib.internal.httpx.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/jinja2/__init__.py b/ddtrace/contrib/jinja2/__init__.py index af2f6580041..b10c98609a8 100644 --- a/ddtrace/contrib/jinja2/__init__.py +++ b/ddtrace/contrib/jinja2/__init__.py @@ -27,23 +27,19 @@ By default, the service name is set to None, so it is inherited from the parent span. If there is no parent span and the service name is not overridden the agent will drop the traces. """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.jinja2.patch` directly +import warnings as _w -required_modules = ["jinja2"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.jinja2.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.jinja2.patch import get_version +from ddtrace.contrib.internal.jinja2.patch import patch +from ddtrace.contrib.internal.jinja2.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.jinja2.patch import get_version - from ddtrace.contrib.internal.jinja2.patch import patch - from ddtrace.contrib.internal.jinja2.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/kafka/__init__.py b/ddtrace/contrib/kafka/__init__.py index e784e6c9e52..f3cf66f6f23 100644 --- a/ddtrace/contrib/kafka/__init__.py +++ b/ddtrace/contrib/kafka/__init__.py @@ -41,23 +41,20 @@ Pin.override(confluent_kafka, service="custom-service-name") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["confluent_kafka"] +# Required to allow users to import from `ddtrace.contrib.kafka.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.kafka.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.kafka.patch import get_version - from ddtrace.contrib.internal.kafka.patch import patch - from ddtrace.contrib.internal.kafka.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.kafka.patch import get_version +from ddtrace.contrib.internal.kafka.patch import patch +from ddtrace.contrib.internal.kafka.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/langchain/__init__.py b/ddtrace/contrib/langchain/__init__.py index ba42e44af75..9f306fe07ed 100644 --- a/ddtrace/contrib/langchain/__init__.py +++ b/ddtrace/contrib/langchain/__init__.py @@ -207,23 +207,19 @@ Default: ``0.1`` """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.langchain.patch` directly +import warnings as _w -required_modules = ["langchain"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.langchain.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.langchain.patch import get_version +from ddtrace.contrib.internal.langchain.patch import patch +from ddtrace.contrib.internal.langchain.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.langchain.patch import get_version - from ddtrace.contrib.internal.langchain.patch import patch - from ddtrace.contrib.internal.langchain.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/logbook/__init__.py b/ddtrace/contrib/logbook/__init__.py index 1257af21a07..d42491706f2 100644 --- a/ddtrace/contrib/logbook/__init__.py +++ b/ddtrace/contrib/logbook/__init__.py @@ -48,23 +48,19 @@ https://docs.datadoghq.com/logs/log_collection/python/ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.logbook.patch` directly +import warnings as _w -required_modules = ["logbook"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.logbook.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.logbook.patch import get_version +from ddtrace.contrib.internal.logbook.patch import patch +from ddtrace.contrib.internal.logbook.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.logbook.patch import get_version - from ddtrace.contrib.internal.logbook.patch import patch - from ddtrace.contrib.internal.logbook.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/logging/__init__.py b/ddtrace/contrib/logging/__init__.py index 137a8f1a1af..24deca593a5 100644 --- a/ddtrace/contrib/logging/__init__.py +++ b/ddtrace/contrib/logging/__init__.py @@ -61,23 +61,19 @@ def hello(): https://docs.datadoghq.com/logs/guide/logs-not-showing-expected-timestamp/ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.logging.patch` directly +import warnings as _w -required_modules = ["logging"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.logging.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.logging.patch import get_version +from ddtrace.contrib.internal.logging.patch import patch +from ddtrace.contrib.internal.logging.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.logging.patch import get_version - from ddtrace.contrib.internal.logging.patch import patch - from ddtrace.contrib.internal.logging.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/loguru/__init__.py b/ddtrace/contrib/loguru/__init__.py index ef4cf4e8cc0..4fb56886108 100644 --- a/ddtrace/contrib/loguru/__init__.py +++ b/ddtrace/contrib/loguru/__init__.py @@ -63,23 +63,19 @@ def log_format(record): https://docs.datadoghq.com/logs/log_collection/python/ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.loguru.patch` directly +import warnings as _w -required_modules = ["loguru"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.loguru.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.loguru.patch import get_version +from ddtrace.contrib.internal.loguru.patch import patch +from ddtrace.contrib.internal.loguru.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.loguru.patch import get_version - from ddtrace.contrib.internal.loguru.patch import patch - from ddtrace.contrib.internal.loguru.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/mako/__init__.py b/ddtrace/contrib/mako/__init__.py index e76fabc7e2a..2dda665270e 100644 --- a/ddtrace/contrib/mako/__init__.py +++ b/ddtrace/contrib/mako/__init__.py @@ -10,23 +10,20 @@ t = Template(filename="index.html") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["mako"] +# Required to allow users to import from `ddtrace.contrib.mako.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.mako.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.mako.patch import get_version - from ddtrace.contrib.internal.mako.patch import patch - from ddtrace.contrib.internal.mako.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.mako.patch import get_version +from ddtrace.contrib.internal.mako.patch import patch +from ddtrace.contrib.internal.mako.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/mariadb/__init__.py b/ddtrace/contrib/mariadb/__init__.py index 976514fadc9..e5c7139ee74 100644 --- a/ddtrace/contrib/mariadb/__init__.py +++ b/ddtrace/contrib/mariadb/__init__.py @@ -52,23 +52,20 @@ cursor.execute("SELECT 6*7 AS the_answer;") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["mariadb"] +# Required to allow users to import from `ddtrace.contrib.mariadb.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.mariadb.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.mariadb.patch import get_version - from ddtrace.contrib.internal.mariadb.patch import patch - from ddtrace.contrib.internal.mariadb.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.mariadb.patch import get_version +from ddtrace.contrib.internal.mariadb.patch import patch +from ddtrace.contrib.internal.mariadb.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/molten/__init__.py b/ddtrace/contrib/molten/__init__.py index 624f13ac8e7..4116e4fd8c7 100644 --- a/ddtrace/contrib/molten/__init__.py +++ b/ddtrace/contrib/molten/__init__.py @@ -34,23 +34,20 @@ def hello(name: str, age: int) -> str: :ref:`All HTTP tags ` are supported for this integration. """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["molten"] +# Required to allow users to import from `ddtrace.contrib.molten.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.molten.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.molten.patch import get_version - from ddtrace.contrib.internal.molten.patch import patch - from ddtrace.contrib.internal.molten.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.molten.patch import get_version +from ddtrace.contrib.internal.molten.patch import patch +from ddtrace.contrib.internal.molten.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/mongoengine/__init__.py b/ddtrace/contrib/mongoengine/__init__.py index a7ded2176f3..1522ac1438b 100644 --- a/ddtrace/contrib/mongoengine/__init__.py +++ b/ddtrace/contrib/mongoengine/__init__.py @@ -17,22 +17,18 @@ Pin.override(client, service="mongo-master") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.mongoengine.patch` directly +import warnings as _w -required_modules = ["mongoengine"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.mongoengine.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.mongoengine.patch import get_version +from ddtrace.contrib.internal.mongoengine.patch import patch - # Expose public methods - from ddtrace.contrib.internal.mongoengine.patch import get_version - from ddtrace.contrib.internal.mongoengine.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/mysql/__init__.py b/ddtrace/contrib/mysql/__init__.py index 2c43bec69c0..1c3f6064e55 100644 --- a/ddtrace/contrib/mysql/__init__.py +++ b/ddtrace/contrib/mysql/__init__.py @@ -62,23 +62,22 @@ Help on mysql.connector can be found on: https://dev.mysql.com/doc/connector-python/en/ """ -from ddtrace.internal.utils.importlib import require_modules # check `mysql-connector` availability -required_modules = ["mysql.connector"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.mysql.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Required to allow users to import from `ddtrace.contrib.mysql.patch` directly +import warnings as _w - # Expose public methods - from ddtrace.contrib.internal.mysql.patch import get_version - from ddtrace.contrib.internal.mysql.patch import patch - __all__ = ["patch", "get_version"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 + +# Expose public methods +from ddtrace.contrib.internal.mysql.patch import get_version +from ddtrace.contrib.internal.mysql.patch import patch + + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/mysqldb/__init__.py b/ddtrace/contrib/mysqldb/__init__.py index 6201e7016d7..17bb76b08a8 100644 --- a/ddtrace/contrib/mysqldb/__init__.py +++ b/ddtrace/contrib/mysqldb/__init__.py @@ -75,21 +75,18 @@ https://mysqlclient.readthedocs.io/ """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["MySQLdb"] +# Required to allow users to import from `ddtrace.contrib.mysqldb.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.mysqldb.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.mysqldb.patch import get_version - from ddtrace.contrib.internal.mysqldb.patch import patch +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.mysqldb.patch import get_version +from ddtrace.contrib.internal.mysqldb.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/openai/__init__.py b/ddtrace/contrib/openai/__init__.py index 0f3da05407d..79a5b488834 100644 --- a/ddtrace/contrib/openai/__init__.py +++ b/ddtrace/contrib/openai/__init__.py @@ -246,23 +246,20 @@ Pin.override(openai, service="my-openai-service") """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["openai"] +# Required to allow users to import from `ddtrace.contrib.openai.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.openai.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.openai.patch import get_version - from ddtrace.contrib.internal.openai.patch import patch - from ddtrace.contrib.internal.openai.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.openai.patch import get_version +from ddtrace.contrib.internal.openai.patch import patch +from ddtrace.contrib.internal.openai.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/protobuf/__init__.py b/ddtrace/contrib/protobuf/__init__.py index 4284782cdbf..dd429d3aa83 100644 --- a/ddtrace/contrib/protobuf/__init__.py +++ b/ddtrace/contrib/protobuf/__init__.py @@ -15,16 +15,10 @@ ~~~~~~~~~~~~~ """ -from ...internal.utils.importlib import require_modules +# Expose public methods +from ..internal.protobuf.patch import get_version +from ..internal.protobuf.patch import patch +from ..internal.protobuf.patch import unpatch -required_modules = ["protobuf"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Expose public methods - from ..internal.protobuf.patch import get_version - from ..internal.protobuf.patch import patch - from ..internal.protobuf.patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/pylibmc/__init__.py b/ddtrace/contrib/pylibmc/__init__.py index c7d397153b7..5689fcd9070 100644 --- a/ddtrace/contrib/pylibmc/__init__.py +++ b/ddtrace/contrib/pylibmc/__init__.py @@ -19,22 +19,18 @@ Pin.override(client, service="memcached-sessions") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.pylibmc.patch` directly +import warnings as _w -required_modules = ["pylibmc"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pylibmc.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.pylibmc.client import TracedClient +from ddtrace.contrib.internal.pylibmc.patch import get_version +from ddtrace.contrib.internal.pylibmc.patch import patch - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.pylibmc.client import TracedClient - from ddtrace.contrib.internal.pylibmc.patch import get_version - from ddtrace.contrib.internal.pylibmc.patch import patch - __all__ = ["TracedClient", "patch", "get_version"] +__all__ = ["TracedClient", "patch", "get_version"] diff --git a/ddtrace/contrib/pymemcache/__init__.py b/ddtrace/contrib/pymemcache/__init__.py index 937b6688895..871d8ee0f6c 100644 --- a/ddtrace/contrib/pymemcache/__init__.py +++ b/ddtrace/contrib/pymemcache/__init__.py @@ -30,22 +30,19 @@ Pymemcache ``HashClient`` will also be indirectly patched as it uses ``Client`` under the hood. """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["pymemcache"] +# Required to allow users to import from `ddtrace.contrib.pymemcache.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pymemcache.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.pymemcache.patch import get_version - from ddtrace.contrib.internal.pymemcache.patch import patch - from ddtrace.contrib.internal.pymemcache.patch import unpatch +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.pymemcache.patch import get_version +from ddtrace.contrib.internal.pymemcache.patch import patch +from ddtrace.contrib.internal.pymemcache.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/pymongo/__init__.py b/ddtrace/contrib/pymongo/__init__.py index 66174b10ff5..60394b6c2f3 100644 --- a/ddtrace/contrib/pymongo/__init__.py +++ b/ddtrace/contrib/pymongo/__init__.py @@ -35,22 +35,19 @@ Default: ``"pymongo"`` """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["pymongo"] +# Required to allow users to import from `ddtrace.contrib.pymongo.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pymongo.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.pymongo.patch import get_version - from ddtrace.contrib.internal.pymongo.patch import patch +# Expose public methods +from ddtrace.contrib.internal.pymongo.patch import get_version +from ddtrace.contrib.internal.pymongo.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/pymysql/__init__.py b/ddtrace/contrib/pymysql/__init__.py index 4e7b322c5d6..d4b24e2cd5f 100644 --- a/ddtrace/contrib/pymysql/__init__.py +++ b/ddtrace/contrib/pymysql/__init__.py @@ -55,22 +55,18 @@ cursor.execute("SELECT 6*7 AS the_answer;") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.pymysql.patch` directly +import warnings as _w -required_modules = ["pymysql"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pymysql.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.pymysql.patch import get_version +from ddtrace.contrib.internal.pymysql.patch import patch - # Expose public methods - from ddtrace.contrib.internal.pymysql.patch import get_version - from ddtrace.contrib.internal.pymysql.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/pynamodb/__init__.py b/ddtrace/contrib/pynamodb/__init__.py index 12c138f5377..3a84e603243 100644 --- a/ddtrace/contrib/pynamodb/__init__.py +++ b/ddtrace/contrib/pynamodb/__init__.py @@ -29,22 +29,17 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.pynamodb.patch` directly +import warnings as _w -required_modules = ["pynamodb.connection.base"] +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pynamodb.patch` directly - import warnings as _w +# Expose public methods +from ddtrace.contrib.internal.pynamodb.patch import get_version +from ddtrace.contrib.internal.pynamodb.patch import patch - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.pynamodb.patch import get_version - from ddtrace.contrib.internal.pynamodb.patch import patch - - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/pyodbc/__init__.py b/ddtrace/contrib/pyodbc/__init__.py index f874f561013..44605b7cdc9 100644 --- a/ddtrace/contrib/pyodbc/__init__.py +++ b/ddtrace/contrib/pyodbc/__init__.py @@ -53,22 +53,19 @@ cursor = db.cursor() cursor.execute("select * from users where id = 1") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["pyodbc"] +# Required to allow users to import from `ddtrace.contrib.pyodbc.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pyodbc.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.pyodbc.patch import get_version - from ddtrace.contrib.internal.pyodbc.patch import patch +# Expose public methods +from ddtrace.contrib.internal.pyodbc.patch import get_version +from ddtrace.contrib.internal.pyodbc.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/pyramid/__init__.py b/ddtrace/contrib/pyramid/__init__.py index b0cb0c9fcb3..72ef2613117 100644 --- a/ddtrace/contrib/pyramid/__init__.py +++ b/ddtrace/contrib/pyramid/__init__.py @@ -40,25 +40,21 @@ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.pyramid.patch` directly +import warnings as _w -required_modules = ["pyramid"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.pyramid.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.pyramid.patch import get_version +from ddtrace.contrib.internal.pyramid.patch import patch +from ddtrace.contrib.internal.pyramid.trace import includeme +from ddtrace.contrib.internal.pyramid.trace import trace_pyramid +from ddtrace.contrib.internal.pyramid.trace import trace_tween_factory - # Expose public methods - from ddtrace.contrib.internal.pyramid.patch import get_version - from ddtrace.contrib.internal.pyramid.patch import patch - from ddtrace.contrib.internal.pyramid.trace import includeme - from ddtrace.contrib.internal.pyramid.trace import trace_pyramid - from ddtrace.contrib.internal.pyramid.trace import trace_tween_factory - __all__ = ["patch", "trace_pyramid", "trace_tween_factory", "includeme", "get_version"] +__all__ = ["patch", "trace_pyramid", "trace_tween_factory", "includeme", "get_version"] diff --git a/ddtrace/contrib/redis/__init__.py b/ddtrace/contrib/redis/__init__.py index 6ab85b1a9fa..4fddef1c742 100644 --- a/ddtrace/contrib/redis/__init__.py +++ b/ddtrace/contrib/redis/__init__.py @@ -67,22 +67,18 @@ client.get("my-key") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.redis.patch` directly +import warnings as _w -required_modules = ["redis", "redis.client"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.redis.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.redis.patch import get_version +from ddtrace.contrib.internal.redis.patch import patch - # Expose public methods - from ddtrace.contrib.internal.redis.patch import get_version - from ddtrace.contrib.internal.redis.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/rediscluster/__init__.py b/ddtrace/contrib/rediscluster/__init__.py index 55d20f9d933..cb14eb9aa30 100644 --- a/ddtrace/contrib/rediscluster/__init__.py +++ b/ddtrace/contrib/rediscluster/__init__.py @@ -48,22 +48,18 @@ Default: ``True`` """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.rediscluster.patch` directly +import warnings as _w -required_modules = ["rediscluster", "rediscluster.client"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.rediscluster.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.rediscluster.patch import get_version +from ddtrace.contrib.internal.rediscluster.patch import patch - # Expose public methods - from ddtrace.contrib.internal.rediscluster.patch import get_version - from ddtrace.contrib.internal.rediscluster.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/requests/__init__.py b/ddtrace/contrib/requests/__init__.py index e9a9e973d77..727e8219339 100644 --- a/ddtrace/contrib/requests/__init__.py +++ b/ddtrace/contrib/requests/__init__.py @@ -72,24 +72,21 @@ cfg['service_name'] = 'auth-api' cfg['distributed_tracing'] = False """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["requests"] +# Required to allow users to import from `ddtrace.contrib.requests.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.requests.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.requests.patch import get_version - from ddtrace.contrib.internal.requests.patch import patch - from ddtrace.contrib.internal.requests.patch import unpatch - from ddtrace.contrib.internal.requests.session import TracedSession +# Expose public methods +from ddtrace.contrib.internal.requests.patch import get_version +from ddtrace.contrib.internal.requests.patch import patch +from ddtrace.contrib.internal.requests.patch import unpatch +from ddtrace.contrib.internal.requests.session import TracedSession - __all__ = ["patch", "unpatch", "TracedSession", "get_version"] + +__all__ = ["patch", "unpatch", "TracedSession", "get_version"] diff --git a/ddtrace/contrib/sanic/__init__.py b/ddtrace/contrib/sanic/__init__.py index b68db2efe1c..96f47d156ac 100644 --- a/ddtrace/contrib/sanic/__init__.py +++ b/ddtrace/contrib/sanic/__init__.py @@ -55,23 +55,20 @@ def index(request): .. __: https://sanic.readthedocs.io/en/latest/ """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["sanic"] +# Required to allow users to import from `ddtrace.contrib.sanic.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.sanic.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.sanic.patch import get_version - from ddtrace.contrib.internal.sanic.patch import patch - from ddtrace.contrib.internal.sanic.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.sanic.patch import get_version +from ddtrace.contrib.internal.sanic.patch import patch +from ddtrace.contrib.internal.sanic.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/selenium/__init__.py b/ddtrace/contrib/selenium/__init__.py index d30c896fdd7..f5016ce0254 100644 --- a/ddtrace/contrib/selenium/__init__.py +++ b/ddtrace/contrib/selenium/__init__.py @@ -22,16 +22,10 @@ DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: The time in milliseconds to wait after flushing the RUM session. """ -from ...internal.utils.importlib import require_modules +# Expose public methods +from ..internal.selenium.patch import get_version +from ..internal.selenium.patch import patch +from ..internal.selenium.patch import unpatch -required_modules = ["selenium"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Expose public methods - from ..internal.selenium.patch import get_version - from ..internal.selenium.patch import patch - from ..internal.selenium.patch import unpatch - - __all__ = ["get_version", "patch", "unpatch"] +__all__ = ["get_version", "patch", "unpatch"] diff --git a/ddtrace/contrib/snowflake/__init__.py b/ddtrace/contrib/snowflake/__init__.py index c89f42cf8c4..e675ff7a067 100644 --- a/ddtrace/contrib/snowflake/__init__.py +++ b/ddtrace/contrib/snowflake/__init__.py @@ -58,23 +58,20 @@ cursor = conn.cursor() cursor.execute("SELECT current_version()") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["snowflake.connector"] +# Required to allow users to import from `ddtrace.contrib.snowflake.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.snowflake.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.snowflake.patch import get_version - from ddtrace.contrib.internal.snowflake.patch import patch - from ddtrace.contrib.internal.snowflake.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.snowflake.patch import get_version +from ddtrace.contrib.internal.snowflake.patch import patch +from ddtrace.contrib.internal.snowflake.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/sqlalchemy/__init__.py b/ddtrace/contrib/sqlalchemy/__init__.py index 15d05aa45f6..c294b8c976c 100644 --- a/ddtrace/contrib/sqlalchemy/__init__.py +++ b/ddtrace/contrib/sqlalchemy/__init__.py @@ -19,24 +19,21 @@ # Use a PIN to specify metadata related to this engine Pin.override(engine, service='replica-db') """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["sqlalchemy", "sqlalchemy.event"] +# Required to allow users to import from `ddtrace.contrib.sqlalchemy.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.sqlalchemy.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.sqlalchemy.engine import trace_engine - from ddtrace.contrib.internal.sqlalchemy.patch import get_version - from ddtrace.contrib.internal.sqlalchemy.patch import patch - from ddtrace.contrib.internal.sqlalchemy.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.sqlalchemy.engine import trace_engine +from ddtrace.contrib.internal.sqlalchemy.patch import get_version +from ddtrace.contrib.internal.sqlalchemy.patch import patch +from ddtrace.contrib.internal.sqlalchemy.patch import unpatch - __all__ = ["trace_engine", "patch", "unpatch", "get_version"] + +__all__ = ["trace_engine", "patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/sqlite3/__init__.py b/ddtrace/contrib/sqlite3/__init__.py index 02624851300..42499cf0447 100644 --- a/ddtrace/contrib/sqlite3/__init__.py +++ b/ddtrace/contrib/sqlite3/__init__.py @@ -53,22 +53,19 @@ cursor = db.cursor() cursor.execute("select * from users where id = 1") """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["sqlite3"] +# Required to allow users to import from `ddtrace.contrib.sqlite3.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.sqlite3.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.sqlite3.patch import get_version - from ddtrace.contrib.internal.sqlite3.patch import patch +# Expose public methods +from ddtrace.contrib.internal.sqlite3.patch import get_version +from ddtrace.contrib.internal.sqlite3.patch import patch - __all__ = ["patch", "get_version"] + +__all__ = ["patch", "get_version"] diff --git a/ddtrace/contrib/starlette/__init__.py b/ddtrace/contrib/starlette/__init__.py index 9413e507759..d3327feded4 100644 --- a/ddtrace/contrib/starlette/__init__.py +++ b/ddtrace/contrib/starlette/__init__.py @@ -57,23 +57,20 @@ config.starlette['request_span_name'] = 'custom-request-span-name' """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["starlette"] +# Required to allow users to import from `ddtrace.contrib.starlette.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.starlette.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.starlette.patch import get_version - from ddtrace.contrib.internal.starlette.patch import patch - from ddtrace.contrib.internal.starlette.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.starlette.patch import get_version +from ddtrace.contrib.internal.starlette.patch import patch +from ddtrace.contrib.internal.starlette.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/structlog/__init__.py b/ddtrace/contrib/structlog/__init__.py index c953b974f34..0a9bcb56395 100644 --- a/ddtrace/contrib/structlog/__init__.py +++ b/ddtrace/contrib/structlog/__init__.py @@ -38,23 +38,19 @@ https://docs.datadoghq.com/logs/log_collection/python/ """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.structlog.patch` directly +import warnings as _w -required_modules = ["structlog"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.structlog.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.structlog.patch import get_version +from ddtrace.contrib.internal.structlog.patch import patch +from ddtrace.contrib.internal.structlog.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.structlog.patch import get_version - from ddtrace.contrib.internal.structlog.patch import patch - from ddtrace.contrib.internal.structlog.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/subprocess/__init__.py b/ddtrace/contrib/subprocess/__init__.py index ea0faae85aa..2e4969648d1 100644 --- a/ddtrace/contrib/subprocess/__init__.py +++ b/ddtrace/contrib/subprocess/__init__.py @@ -19,23 +19,19 @@ ```ddtrace.contrib.subprocess.constants.SENSITIVE_WORDS_WILDCARDS```. """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.subprocess.patch` directly +import warnings as _w -required_modules = ["os", "subprocess"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.subprocess.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.subprocess.patch import get_version +from ddtrace.contrib.internal.subprocess.patch import patch +from ddtrace.contrib.internal.subprocess.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.subprocess.patch import get_version - from ddtrace.contrib.internal.subprocess.patch import patch - from ddtrace.contrib.internal.subprocess.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/tornado/__init__.py b/ddtrace/contrib/tornado/__init__.py index 9d681396810..ad0adef2dd5 100644 --- a/ddtrace/contrib/tornado/__init__.py +++ b/ddtrace/contrib/tornado/__init__.py @@ -104,33 +104,30 @@ def log_exception(self, typ, value, tb): * ``agent_port`` (default: `8126`): define the port of the APM agent. * ``settings`` (default: ``{}``): Tracer extra settings used to change, for instance, the filtering behavior. """ -from ddtrace.internal.utils.importlib import require_modules - - -required_modules = ["tornado"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.tornado.patch` directly - import warnings as _w - - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 - - # Expose public methods - from ddtrace.contrib.internal.tornado.patch import get_version - from ddtrace.contrib.internal.tornado.patch import patch - from ddtrace.contrib.internal.tornado.patch import unpatch - from ddtrace.contrib.internal.tornado.stack_context import TracerStackContext - from ddtrace.contrib.internal.tornado.stack_context import context_provider - from ddtrace.contrib.internal.tornado.stack_context import run_with_trace_context - - __all__ = [ - "patch", - "unpatch", - "context_provider", - "run_with_trace_context", - "TracerStackContext", - "get_version", - ] + + +# Required to allow users to import from `ddtrace.contrib.tornado.patch` directly +import warnings as _w + + +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 + +# Expose public methods +from ddtrace.contrib.internal.tornado.patch import get_version +from ddtrace.contrib.internal.tornado.patch import patch +from ddtrace.contrib.internal.tornado.patch import unpatch +from ddtrace.contrib.internal.tornado.stack_context import TracerStackContext +from ddtrace.contrib.internal.tornado.stack_context import context_provider +from ddtrace.contrib.internal.tornado.stack_context import run_with_trace_context + + +__all__ = [ + "patch", + "unpatch", + "context_provider", + "run_with_trace_context", + "TracerStackContext", + "get_version", +] diff --git a/ddtrace/contrib/unittest/__init__.py b/ddtrace/contrib/unittest/__init__.py index cc15ea375c3..5180b59c959 100644 --- a/ddtrace/contrib/unittest/__init__.py +++ b/ddtrace/contrib/unittest/__init__.py @@ -34,15 +34,11 @@ Default: ``True`` """ -from ddtrace.internal.utils.importlib import require_modules + from .patch import get_version from .patch import patch from .patch import unpatch -required_modules = ["unittest"] - -with require_modules(required_modules) as missing_modules: - if not missing_modules: - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/urllib/__init__.py b/ddtrace/contrib/urllib/__init__.py index 8f22672d3d9..596251e99ad 100644 --- a/ddtrace/contrib/urllib/__init__.py +++ b/ddtrace/contrib/urllib/__init__.py @@ -4,23 +4,20 @@ if ``DD_IAST_ENABLED`` is set to ``True`` (for detecting sink points) and/or ``DD_ASM_ENABLED`` is set to ``True`` (for exploit prevention). """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["urllib"] +# Required to allow users to import from `ddtrace.contrib.urllib.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.urllib.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.urllib.patch import get_version - from ddtrace.contrib.internal.urllib.patch import patch - from ddtrace.contrib.internal.urllib.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.urllib.patch import get_version +from ddtrace.contrib.internal.urllib.patch import patch +from ddtrace.contrib.internal.urllib.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/urllib3/__init__.py b/ddtrace/contrib/urllib3/__init__.py index 71f6ad2eebe..0f2ebf2b12f 100644 --- a/ddtrace/contrib/urllib3/__init__.py +++ b/ddtrace/contrib/urllib3/__init__.py @@ -50,23 +50,20 @@ Default: ``False`` """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["urllib3"] +# Required to allow users to import from `ddtrace.contrib.urllib3.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.urllib3.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.urllib3.patch import get_version - from ddtrace.contrib.internal.urllib3.patch import patch - from ddtrace.contrib.internal.urllib3.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.urllib3.patch import get_version +from ddtrace.contrib.internal.urllib3.patch import patch +from ddtrace.contrib.internal.urllib3.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/vertexai/__init__.py b/ddtrace/contrib/vertexai/__init__.py index acc2417b679..f472d28790d 100644 --- a/ddtrace/contrib/vertexai/__init__.py +++ b/ddtrace/contrib/vertexai/__init__.py @@ -82,15 +82,10 @@ Pin.override(vertexai, service="my-vertexai-service") """ # noqa: E501 -from ddtrace.internal.utils.importlib import require_modules +from ddtrace.contrib.internal.vertexai.patch import get_version +from ddtrace.contrib.internal.vertexai.patch import patch +from ddtrace.contrib.internal.vertexai.patch import unpatch -required_modules = ["vertexai"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - from ddtrace.contrib.internal.vertexai.patch import get_version - from ddtrace.contrib.internal.vertexai.patch import patch - from ddtrace.contrib.internal.vertexai.patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/vertica/__init__.py b/ddtrace/contrib/vertica/__init__.py index dfafe39a79f..3ec424fbb53 100644 --- a/ddtrace/contrib/vertica/__init__.py +++ b/ddtrace/contrib/vertica/__init__.py @@ -39,23 +39,19 @@ Pin.override(conn, service='myverticaservice', tracer=custom_tracer) """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.vertica.patch` directly +import warnings as _w -required_modules = ["vertica_python"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.vertica.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.vertica.patch import get_version +from ddtrace.contrib.internal.vertica.patch import patch +from ddtrace.contrib.internal.vertica.patch import unpatch - # Expose public methods - from ddtrace.contrib.internal.vertica.patch import get_version - from ddtrace.contrib.internal.vertica.patch import patch - from ddtrace.contrib.internal.vertica.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/webbrowser/__init__.py b/ddtrace/contrib/webbrowser/__init__.py index e5123725b0c..49a251cad50 100644 --- a/ddtrace/contrib/webbrowser/__init__.py +++ b/ddtrace/contrib/webbrowser/__init__.py @@ -4,23 +4,20 @@ if ``DD_IAST_ENABLED`` is set to ``True`` (for detecting sink points) and/or ``DD_ASM_ENABLED`` is set to ``True`` (for exploit prevention). """ -from ddtrace.internal.utils.importlib import require_modules -required_modules = ["webbrowser"] +# Required to allow users to import from `ddtrace.contrib.webbrowser.patch` directly +import warnings as _w -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.webbrowser.patch` directly - import warnings as _w - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.webbrowser.patch import get_version - from ddtrace.contrib.internal.webbrowser.patch import patch - from ddtrace.contrib.internal.webbrowser.patch import unpatch +# Expose public methods +from ddtrace.contrib.internal.webbrowser.patch import get_version +from ddtrace.contrib.internal.webbrowser.patch import patch +from ddtrace.contrib.internal.webbrowser.patch import unpatch - __all__ = ["patch", "unpatch", "get_version"] + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/yaaredis/__init__.py b/ddtrace/contrib/yaaredis/__init__.py index 05396dae7a3..03d76db11c0 100644 --- a/ddtrace/contrib/yaaredis/__init__.py +++ b/ddtrace/contrib/yaaredis/__init__.py @@ -66,22 +66,18 @@ async def example(): await client.get("my-key") """ -from ddtrace.internal.utils.importlib import require_modules +# Required to allow users to import from `ddtrace.contrib.yaaredis.patch` directly +import warnings as _w -required_modules = ["yaaredis", "yaaredis.client"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - # Required to allow users to import from `ddtrace.contrib.yaaredis.patch` directly - import warnings as _w +with _w.catch_warnings(): + _w.simplefilter("ignore", DeprecationWarning) + from . import patch as _ # noqa: F401, I001 - with _w.catch_warnings(): - _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 +# Expose public methods +from ddtrace.contrib.internal.yaaredis.patch import get_version +from ddtrace.contrib.internal.yaaredis.patch import patch - # Expose public methods - from ddtrace.contrib.internal.yaaredis.patch import get_version - from ddtrace.contrib.internal.yaaredis.patch import patch - __all__ = ["patch", "get_version"] +__all__ = ["patch", "get_version"] diff --git a/docs/conf.py b/docs/conf.py index 84ebe856954..9a20184de0a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -140,6 +140,12 @@ def _skip(self, word): # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# autodoc_mock_imports contains a list of modules to be mocked up. +# This is useful when some external dependencies are installed at build time and break the building process. +# The following modules require third party packages and should be mocked when generating docs: +autodoc_mock_imports = ["ddtrace.contrib.internal"] + # The reST default role (used for this markup: `text`) to use for all # documents. # diff --git a/templates/integration/__init__.py b/templates/integration/__init__.py index 1fb943b8226..ef19d262e58 100644 --- a/templates/integration/__init__.py +++ b/templates/integration/__init__.py @@ -40,15 +40,10 @@ myfoo = foo.Foo() Pin.override(myfoo, service="myfoo") """ -from ...internal.utils.importlib import require_modules +from .patch import get_version +from .patch import patch +from .patch import unpatch -required_modules = ["foo"] -with require_modules(required_modules) as missing_modules: - if not missing_modules: - from .patch import get_version - from .patch import patch - from .patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] +__all__ = ["patch", "unpatch", "get_version"] From 00cd9fd0df5000525356b95929a3f51d7f21b78e Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 2 Jan 2025 13:52:58 -0500 Subject: [PATCH 369/372] chore(tracer): remove asm properties from the tracer class (#11791) ## Description Removes the following tracer fields and replaces all usages with the corresponding `asm_config`: - Tracer._asm_enabled - Tracer._iast_enabled - Tracer._appsec_standalone_enabled Next steps: - Remove all asm, profiling, and serverless specific logic from `ddtrace/_trace/tracer.py`. - Define a product level configure methods - Introduce product level processors and then product specific logic. - Deprecate calling tracing.configure(....) for non tracing features. ## Motivation - Decouples asm configurations from tracing - Avoids storing duplicate references for global configurations. Ideally global configurations should have once source of truth. This will help simplify remote configuration. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/_trace/tracer.py | 60 +++++++--------- ddtrace/appsec/_remoteconfiguration.py | 12 +--- .../contrib/internal/requests/connection.py | 3 +- ddtrace/pin.py | 5 +- ddtrace/settings/asm.py | 6 ++ tests/appsec/appsec/test_asm_standalone.py | 2 - tests/appsec/contrib_appsec/utils.py | 2 - tests/appsec/utils.py | 3 - tests/contrib/django/test_django_appsec.py | 2 - .../contrib/django/test_django_appsec_iast.py | 3 - .../djangorestframework/test_appsec.py | 1 - tests/contrib/fastapi/test_fastapi_appsec.py | 1 - .../fastapi/test_fastapi_appsec_iast.py | 1 - tests/contrib/flask/test_flask_appsec.py | 1 - tests/contrib/flask/test_flask_appsec_iast.py | 6 +- .../flask/test_flask_appsec_telemetry.py | 1 - .../requests/test_requests_distributed.py | 68 ++++++++++--------- tests/contrib/urllib3/test_urllib3.py | 2 - tests/tracer/test_tracer.py | 7 -- 19 files changed, 74 insertions(+), 112 deletions(-) diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index fa0c89cdd7f..f815e0f184e 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -106,14 +106,11 @@ def _default_span_processors_factory( trace_writer: TraceWriter, partial_flush_enabled: bool, partial_flush_min_spans: int, - appsec_enabled: bool, - iast_enabled: bool, compute_stats_enabled: bool, single_span_sampling_rules: List[SpanSamplingRule], agent_url: str, trace_sampler: BaseSampler, profiling_span_processor: EndpointCallCounterProcessor, - apm_opt_out: bool = False, ) -> Tuple[List[SpanProcessor], Optional[Any], List[SpanProcessor]]: # FIXME: type should be AppsecSpanProcessor but we have a cyclic import here """Construct the default list of span processors to use.""" @@ -121,7 +118,9 @@ def _default_span_processors_factory( trace_processors += [ PeerServiceProcessor(_ps_config), BaseServiceProcessor(), - TraceSamplingProcessor(compute_stats_enabled, trace_sampler, single_span_sampling_rules, apm_opt_out), + TraceSamplingProcessor( + compute_stats_enabled, trace_sampler, single_span_sampling_rules, asm_config._apm_opt_out + ), TraceTagsProcessor(), ] trace_processors += trace_filters @@ -130,7 +129,7 @@ def _default_span_processors_factory( span_processors += [TopLevelSpanProcessor()] if asm_config._asm_libddwaf_available: - if appsec_enabled: + if asm_config._asm_enabled: if asm_config._api_security_enabled: from ddtrace.appsec._api_security.api_manager import APIManager @@ -152,7 +151,7 @@ def _default_span_processors_factory( else: appsec_processor = None - if iast_enabled: + if asm_config._iast_enabled: from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor span_processors.append(AppSecIastSpanProcessor()) @@ -229,14 +228,8 @@ def __init__( self.context_provider = context_provider or DefaultContextProvider() # _user_sampler is the backup in case we need to revert from remote config to local self._user_sampler: Optional[BaseSampler] = DatadogSampler() - self._asm_enabled = asm_config._asm_enabled - self._iast_enabled = asm_config._iast_enabled - self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled self._dogstatsd_url = agent.get_stats_url() if dogstatsd_url is None else dogstatsd_url - self._apm_opt_out = self._appsec_standalone_enabled and ( - self._asm_enabled or self._iast_enabled or config._sca_enabled - ) - if self._apm_opt_out: + if asm_config._apm_opt_out: self.enabled = False # Disable compute stats (neither agent or tracer should compute them) config._trace_compute_stats = False @@ -257,8 +250,10 @@ def __init__( agent_url=self._agent_url, dogstatsd=get_dogstatsd_client(self._dogstatsd_url), sync_mode=self._use_sync_mode(), - headers={"Datadog-Client-Computed-Stats": "yes"} if (self._compute_stats or self._apm_opt_out) else {}, - report_metrics=not self._apm_opt_out, + headers={"Datadog-Client-Computed-Stats": "yes"} + if (self._compute_stats or asm_config._apm_opt_out) + else {}, + report_metrics=not asm_config._apm_opt_out, response_callback=self._agent_response_callback, ) self._single_span_sampling_rules: List[SpanSamplingRule] = get_span_sampling_rules() @@ -266,21 +261,17 @@ def __init__( self._partial_flush_enabled = config._partial_flush_enabled self._partial_flush_min_spans = config._partial_flush_min_spans # Direct link to the appsec processor - self._appsec_processor = None self._endpoint_call_counter_span_processor = EndpointCallCounterProcessor() self._span_processors, self._appsec_processor, self._deferred_processors = _default_span_processors_factory( self._filters, self._writer, self._partial_flush_enabled, self._partial_flush_min_spans, - self._asm_enabled, - self._iast_enabled, self._compute_stats, self._single_span_sampling_rules, self._agent_url, self._sampler, self._endpoint_call_counter_span_processor, - self._apm_opt_out, ) if config._data_streams_enabled: # Inline the import to avoid pulling in ddsketch or protobuf @@ -335,7 +326,7 @@ def sampler(self, value): https://ddtrace.readthedocs.io/en/stable/configuration.html#DD_TRACE_SAMPLING_RULES""", category=DDTraceDeprecationWarning, ) - if self._apm_opt_out: + if asm_config._apm_opt_out: log.warning("Cannot set a custom sampler with Standalone ASM mode") return self._sampler = value @@ -407,7 +398,7 @@ def get_log_correlation_context(self, active: Optional[Union[Context, Span]] = N span id of the current active span, as well as the configured service, version, and environment names. If there is no active span, a dictionary with an empty string for each value will be returned. """ - if active is None and (self.enabled or self._apm_opt_out): + if active is None and (self.enabled or asm_config._apm_opt_out): active = self.context_provider.active() if isinstance(active, Span) and active.service: @@ -489,16 +480,15 @@ def configure( self._partial_flush_min_spans = partial_flush_min_spans if appsec_enabled is not None: - self._asm_enabled = asm_config._asm_enabled = appsec_enabled + asm_config._asm_enabled = appsec_enabled if iast_enabled is not None: - self._iast_enabled = asm_config._iast_enabled = iast_enabled + asm_config._iast_enabled = iast_enabled if appsec_standalone_enabled is not None: - self._appsec_standalone_enabled = asm_config._appsec_standalone_enabled = appsec_standalone_enabled + asm_config._appsec_standalone_enabled = appsec_standalone_enabled - if self._appsec_standalone_enabled and (self._asm_enabled or self._iast_enabled or config._sca_enabled): - self._apm_opt_out = True + if asm_config._apm_opt_out: self.enabled = False # Disable compute stats (neither agent or tracer should compute them) config._trace_compute_stats = False @@ -557,7 +547,7 @@ def configure( if writer is not None: self._writer = writer elif any(x is not None for x in [new_url, api_version, sampler, dogstatsd_url, appsec_enabled]): - if self._asm_enabled: + if asm_config._asm_enabled: api_version = "v0.4" self._writer = AgentWriter( self._agent_url, @@ -566,9 +556,11 @@ def configure( api_version=api_version, # if apm opt out, neither agent or tracer should compute the stats headers=( - {"Datadog-Client-Computed-Stats": "yes"} if (compute_stats_enabled or self._apm_opt_out) else {} + {"Datadog-Client-Computed-Stats": "yes"} + if (compute_stats_enabled or asm_config._apm_opt_out) + else {} ), - report_metrics=not self._apm_opt_out, + report_metrics=not asm_config._apm_opt_out, response_callback=self._agent_response_callback, ) elif writer is None and isinstance(self._writer, LogWriter): @@ -601,14 +593,11 @@ def configure( self._writer, self._partial_flush_enabled, self._partial_flush_min_spans, - self._asm_enabled, - self._iast_enabled, self._compute_stats, self._single_span_sampling_rules, self._agent_url, self._sampler, self._endpoint_call_counter_span_processor, - self._apm_opt_out, ) if context_provider is not None: @@ -667,14 +656,11 @@ def _child_after_fork(self): self._writer, self._partial_flush_enabled, self._partial_flush_min_spans, - self._asm_enabled, - self._iast_enabled, self._compute_stats, self._single_span_sampling_rules, self._agent_url, self._sampler, self._endpoint_call_counter_span_processor, - self._apm_opt_out, ) self._new_process = True @@ -859,7 +845,7 @@ def _start_span( self._services.add(service) # Only call span processors if the tracer is enabled (even if APM opted out) - if self.enabled or self._apm_opt_out: + if self.enabled or asm_config._apm_opt_out: for p in chain(self._span_processors, SpanProcessor.__processors__, self._deferred_processors): p.on_span_start(span) self._hooks.emit(self.__class__.start_span, span) @@ -876,7 +862,7 @@ def _on_span_finish(self, span: Span) -> None: log.debug("span %r closing after its parent %r, this is an error when not using async", span, span._parent) # Only call span processors if the tracer is enabled (even if APM opted out) - if self.enabled or self._apm_opt_out: + if self.enabled or asm_config._apm_opt_out: for p in chain(self._span_processors, SpanProcessor.__processors__, self._deferred_processors): p.on_span_finish(span) diff --git a/ddtrace/appsec/_remoteconfiguration.py b/ddtrace/appsec/_remoteconfiguration.py index 0d470db08c8..1a6fd2e4b6c 100644 --- a/ddtrace/appsec/_remoteconfiguration.py +++ b/ddtrace/appsec/_remoteconfiguration.py @@ -52,12 +52,6 @@ def enable_appsec_rc(test_tracer: Optional[Tracer] = None) -> None: Parameters `test_tracer` and `start_subscribers` are needed for testing purposes """ - # Import tracer here to avoid a circular import - if test_tracer is None: - from ddtrace import tracer - else: - tracer = test_tracer - log.debug("[%s][P: %s] Register ASM Remote Config Callback", os.getpid(), os.getppid()) asm_callback = ( remoteconfig_poller.get_registered(PRODUCTS.ASM_FEATURES) @@ -68,7 +62,7 @@ def enable_appsec_rc(test_tracer: Optional[Tracer] = None) -> None: if _asm_feature_is_required(): remoteconfig_poller.register(PRODUCTS.ASM_FEATURES, asm_callback) - if tracer._asm_enabled and asm_config._asm_static_rule_file is None: + if asm_config._asm_enabled and asm_config._asm_static_rule_file is None: remoteconfig_poller.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking remoteconfig_poller.register(PRODUCTS.ASM, asm_callback) # Exclusion Filters & Custom Rules remoteconfig_poller.register(PRODUCTS.ASM_DD, asm_callback) # DD Rules @@ -226,12 +220,12 @@ def _appsec_1click_activation(features: Mapping[str, Any], test_tracer: Optional ) if rc_asm_enabled: - if not tracer._asm_enabled: + if not asm_config._asm_enabled: tracer.configure(appsec_enabled=True) else: asm_config._asm_enabled = True else: - if tracer._asm_enabled: + if asm_config._asm_enabled: tracer.configure(appsec_enabled=False) else: asm_config._asm_enabled = False diff --git a/ddtrace/contrib/internal/requests/connection.py b/ddtrace/contrib/internal/requests/connection.py index 83c8d82927f..06d3347f0a1 100644 --- a/ddtrace/contrib/internal/requests/connection.py +++ b/ddtrace/contrib/internal/requests/connection.py @@ -15,6 +15,7 @@ from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.internal.utils import get_argument_value from ddtrace.propagation.http import HTTPPropagator +from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) @@ -59,7 +60,7 @@ def _wrap_send(func, instance, args, kwargs): tracer = getattr(instance, "datadog_tracer", ddtrace.tracer) # skip if tracing is not enabled - if not tracer.enabled and not tracer._apm_opt_out: + if not tracer.enabled and not asm_config._apm_opt_out: return func(*args, **kwargs) request = get_argument_value(args, kwargs, 0, "request") diff --git a/ddtrace/pin.py b/ddtrace/pin.py index 7070efcf71c..926918b6cea 100644 --- a/ddtrace/pin.py +++ b/ddtrace/pin.py @@ -144,7 +144,10 @@ def override( def enabled(self): # type: () -> bool """Return true if this pin's tracer is enabled.""" - return bool(self.tracer) and (self.tracer.enabled or self.tracer._apm_opt_out) + # inline to avoid circular imports + from ddtrace.settings.asm import config as asm_config + + return bool(self.tracer) and (self.tracer.enabled or asm_config._apm_opt_out) def onto(self, obj, send=True): # type: (Any, bool) -> None diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index cf20ea08f1a..0aadda674f5 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -269,6 +269,12 @@ def _eval_asm_can_be_enabled(self): def _api_security_feature_active(self) -> bool: return self._asm_libddwaf_available and self._asm_enabled and self._api_security_enabled + @property + def _apm_opt_out(self) -> bool: + return ( + self._asm_enabled or self._iast_enabled or tracer_config._sca_enabled is True + ) and self._appsec_standalone_enabled + @property def _user_event_mode(self) -> str: if self._asm_enabled and self._auto_user_instrumentation_enabled: diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py index 6841314cea8..3c2ed58caf6 100644 --- a/tests/appsec/appsec/test_asm_standalone.py +++ b/tests/appsec/appsec/test_asm_standalone.py @@ -138,8 +138,6 @@ def test_appsec_standalone_apm_enabled_metric(tracer_appsec_standalone): or args.get("iast_enabled", None) or args.get("DD_APPSEC_SCA_ENABLED", "0") == "1" ): - assert tracer._apm_opt_out is True assert span.get_metric("_dd.apm.enabled") == 0.0 else: - assert tracer._apm_opt_out is False assert span.get_metric("_dd.apm.enabled") is None diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 0d195df764e..315caa49a5d 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -88,8 +88,6 @@ def check_rules_triggered(self, rule_id: List[str], root_span): assert result == rule_id, f"result={result}, expected={rule_id}" def update_tracer(self, interface): - interface.tracer._asm_enabled = asm_config._asm_enabled - interface.tracer._iast_enabled = asm_config._iast_enabled interface.tracer.configure(api_version="v0.4") assert asm_config._asm_libddwaf_available # Only for tests diagnostics diff --git a/tests/appsec/utils.py b/tests/appsec/utils.py index 9e09b3ae2ad..9df1065f005 100644 --- a/tests/appsec/utils.py +++ b/tests/appsec/utils.py @@ -6,7 +6,6 @@ from ddtrace._trace.span import Span from ddtrace.ext import SpanTypes import ddtrace.internal.core as core -from ddtrace.settings.asm import config as asm_config from tests.utils import override_global_config @@ -35,8 +34,6 @@ def asm_context( with override_global_config(config) if config else contextlib.nullcontext(): if tracer is None: tracer = default_tracer - if asm_config._asm_enabled: - tracer._asm_enabled = True if config: tracer.configure(api_version="v0.4") diff --git a/tests/contrib/django/test_django_appsec.py b/tests/contrib/django/test_django_appsec.py index fb262918a56..3c5cb399739 100644 --- a/tests/contrib/django/test_django_appsec.py +++ b/tests/contrib/django/test_django_appsec.py @@ -47,8 +47,6 @@ def _aux_appsec_get_root_span( ): if cookies is None: cookies = {} - tracer._asm_enabled = asm_config._asm_enabled - tracer._iast_enabled = asm_config._iast_enabled # Hack: need to pass an argument to configure so that the processors are recreated tracer.configure(api_version="v0.4") # Set cookies diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index 7e42e8aa903..ee5cb069331 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -14,7 +14,6 @@ from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.internal.compat import urlencode -from ddtrace.settings.asm import config as asm_config from tests.appsec.iast.iast_utils import get_line_and_hash from tests.utils import override_env from tests.utils import override_global_config @@ -66,8 +65,6 @@ def _aux_appsec_get_root_span( ): if cookies is None: cookies = {} - tracer._asm_enabled = asm_config._asm_enabled - tracer._iast_enabled = asm_config._iast_enabled # Hack: need to pass an argument to configure so that the processors are recreated tracer.configure(api_version="v0.4") # Set cookies diff --git a/tests/contrib/djangorestframework/test_appsec.py b/tests/contrib/djangorestframework/test_appsec.py index cf2985f32f3..68489f99be0 100644 --- a/tests/contrib/djangorestframework/test_appsec.py +++ b/tests/contrib/djangorestframework/test_appsec.py @@ -11,7 +11,6 @@ @pytest.mark.skipif(django.VERSION < (1, 10), reason="requires django version >= 1.10") def test_djangorest_request_body_urlencoded(client, test_spans, tracer): with override_global_config(dict(_asm_enabled=True)): - tracer._asm_enabled = True # Hack: need to pass an argument to configure so that the processors are recreated tracer.configure(api_version="v0.4") payload = urlencode({"mytestingbody_key": "mytestingbody_value"}) diff --git a/tests/contrib/fastapi/test_fastapi_appsec.py b/tests/contrib/fastapi/test_fastapi_appsec.py index 69284807d09..d2f1b6492f9 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec.py +++ b/tests/contrib/fastapi/test_fastapi_appsec.py @@ -9,7 +9,6 @@ def _aux_appsec_prepare_tracer(tracer, asm_enabled=True): - tracer._asm_enabled = asm_enabled # Hack: need to pass an argument to configure so that the processors are recreated tracer.configure(api_version="v0.4") diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 1a5db995af4..980a1297a69 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -39,7 +39,6 @@ def _aux_appsec_prepare_tracer(tracer): patch_sqlite_sqli() oce.reconfigure() - tracer._iast_enabled = True # Hack: need to pass an argument to configure so that the processors are recreated tracer.configure(api_version="v0.4") diff --git a/tests/contrib/flask/test_flask_appsec.py b/tests/contrib/flask/test_flask_appsec.py index 82433e63b64..7fd045c61f2 100644 --- a/tests/contrib/flask/test_flask_appsec.py +++ b/tests/contrib/flask/test_flask_appsec.py @@ -30,7 +30,6 @@ def setUp(self): patch() def _aux_appsec_prepare_tracer(self, appsec_enabled=True): - self.tracer._asm_enabled = appsec_enabled # Hack: need to pass an argument to configure so that the processors are recreated self.tracer.configure(api_version="v0.4") diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index 238d0630549..94948bde0b2 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -48,9 +48,7 @@ def setUp(self): patch_header_injection() patch_json() - self.tracer._iast_enabled = True - self.tracer._asm_enabled = True - self.tracer.configure(api_version="v0.4") + self.tracer.configure(api_version="v0.4", appsec_enabled=True, iast_enabled=True) oce.reconfigure() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") @@ -1381,8 +1379,6 @@ def setUp(self): ) ): super(FlaskAppSecIASTDisabledTestCase, self).setUp() - self.tracer._iast_enabled = False - self.tracer._asm_enabled = False self.tracer.configure(api_version="v0.4") @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") diff --git a/tests/contrib/flask/test_flask_appsec_telemetry.py b/tests/contrib/flask/test_flask_appsec_telemetry.py index 499d806f56f..df21a2b508b 100644 --- a/tests/contrib/flask/test_flask_appsec_telemetry.py +++ b/tests/contrib/flask/test_flask_appsec_telemetry.py @@ -16,7 +16,6 @@ def inject_fixtures(self, telemetry_writer): # noqa: F811 self.telemetry_writer = telemetry_writer def _aux_appsec_prepare_tracer(self, appsec_enabled=True): - self.tracer._asm_enabled = appsec_enabled # Hack: need to pass an argument to configure so that the processors are recreated self.tracer.configure(api_version="v0.4") diff --git a/tests/contrib/requests/test_requests_distributed.py b/tests/contrib/requests/test_requests_distributed.py index bda32171f6b..b3974700f87 100644 --- a/tests/contrib/requests/test_requests_distributed.py +++ b/tests/contrib/requests/test_requests_distributed.py @@ -1,6 +1,7 @@ from requests_mock import Adapter from ddtrace import config +from ddtrace.settings.asm import config as asm_config from tests.utils import TracerTestCase from tests.utils import get_128_bit_trace_id_from_headers @@ -117,47 +118,48 @@ def matcher(request): def test_propagation_apm_opt_out_true(self): # ensure distributed tracing works when APM is opted out - self.tracer._apm_opt_out = True - self.tracer.enabled = False + with self.override_global_config(dict(_appsec_standalone_enabled=True, _asm_enabled=True)): + assert asm_config._apm_opt_out + self.tracer.enabled = False + cfg = config.get_from(self.session) + cfg["distributed_tracing"] = True + adapter = Adapter() + self.session.mount("mock", adapter) - cfg = config.get_from(self.session) - cfg["distributed_tracing"] = True - adapter = Adapter() - self.session.mount("mock", adapter) + with self.tracer.trace("root") as root: - with self.tracer.trace("root") as root: + def matcher(request): + return self.headers_not_here(self.tracer, request) - def matcher(request): - return self.headers_here(self.tracer, request, root) + adapter.register_uri("GET", "mock://datadog/foo", additional_matcher=matcher, text="bar") + resp = self.session.get("mock://datadog/foo") + assert 200 == resp.status_code + assert "bar" == resp.text - adapter.register_uri("GET", "mock://datadog/foo", additional_matcher=matcher, text="bar") - resp = self.session.get("mock://datadog/foo") - assert 200 == resp.status_code - assert "bar" == resp.text - - spans = self.pop_spans() - root, req = spans - assert "root" == root.name - assert "requests.request" == req.name - assert root.trace_id == req.trace_id - assert root.span_id == req.parent_id + spans = self.pop_spans() + root, req = spans + assert "root" == root.name + assert "requests.request" == req.name + assert root.trace_id == req.trace_id + assert root.span_id == req.parent_id def test_propagation_apm_opt_out_false(self): # ensure distributed tracing doesn't works when APM is disabled but not opted out - self.tracer._apm_opt_out = False - self.tracer.enabled = False + with self.override_global_config(dict(_appsec_standalone_enabled=False, _asm_enabled=True)): + assert not asm_config._apm_opt_out + self.tracer.enabled = False - cfg = config.get_from(self.session) - cfg["distributed_tracing"] = True - adapter = Adapter() - self.session.mount("mock", adapter) + cfg = config.get_from(self.session) + cfg["distributed_tracing"] = True + adapter = Adapter() + self.session.mount("mock", adapter) - with self.tracer.trace("root"): + with self.tracer.trace("root"): - def matcher(request): - return self.headers_not_here(self.tracer, request) + def matcher(request): + return self.headers_not_here(self.tracer, request) - adapter.register_uri("GET", "mock://datadog/foo", additional_matcher=matcher, text="bar") - resp = self.session.get("mock://datadog/foo") - assert 200 == resp.status_code - assert "bar" == resp.text + adapter.register_uri("GET", "mock://datadog/foo", additional_matcher=matcher, text="bar") + resp = self.session.get("mock://datadog/foo") + assert 200 == resp.status_code + assert "bar" == resp.text diff --git a/tests/contrib/urllib3/test_urllib3.py b/tests/contrib/urllib3/test_urllib3.py index 8aa92a4dfb0..2f0c447ee65 100644 --- a/tests/contrib/urllib3/test_urllib3.py +++ b/tests/contrib/urllib3/test_urllib3.py @@ -532,7 +532,6 @@ def test_distributed_tracing_apm_opt_out_true(self): # Check that distributed tracing headers are passed down; raise an error rather than make the # request since we don't care about the response at all config.urllib3["distributed_tracing"] = True - self.tracer._apm_opt_out = True self.tracer.enabled = False with mock.patch( "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError @@ -580,7 +579,6 @@ def test_distributed_tracing_apm_opt_out_true(self): def test_distributed_tracing_apm_opt_out_false(self): """Test with distributed tracing disabled does not propagate the headers""" config.urllib3["distributed_tracing"] = True - self.tracer._apm_opt_out = False self.tracer.enabled = False with mock.patch( "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 4cdcf876aba..cae00259086 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -2054,15 +2054,8 @@ def test_asm_standalone_configuration(sca_enabled, appsec_enabled, iast_enabled) ddtrace.config._reset() tracer = ddtrace.Tracer() tracer.configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) - if appsec_enabled: - assert tracer._asm_enabled is True - if iast_enabled: - assert tracer._iast_enabled is True if sca_enabled == "true": assert bool(ddtrace.config._sca_enabled) is True - - assert tracer._appsec_standalone_enabled is True - assert tracer._apm_opt_out is True assert tracer.enabled is False assert isinstance(tracer._sampler.limiter, RateLimiter) From 6bfe77ede64278fadbd64131fa14ad123417c7ec Mon Sep 17 00:00:00 2001 From: Nick Ripley Date: Thu, 2 Jan 2025 11:26:55 -0800 Subject: [PATCH 370/372] fix(profiling): remove slow getpid call from memalloc path (#11848) memalloc uses getpid to detect whether the process has forked, so that we can unlock the memalloc lock in the child process (if it isn't already locked). Unfortunately the getpid call is quite slow. From the man page: "calls to getpid() always invoke the actual system call, rather than returning a cached value." Furthermore, we _always_ attempt to take the lock for allocations, even if we aren't going to sample them. So this is basically adding a syscall to every allocation. Move this logic out of the allocation path. Switch to using pthread_atfork handlers to ensure that the lock is held prior to forking, and unlock it in the parent and child after forking. This (maybe) has the added benefit of making sure the data structures are in a consistent state in the child process after forking. Unclear if that's an issue prior to this change, though. I may be missing some code that resets the profiler on fork anyway? --- ddtrace/profiling/collector/_memalloc.c | 25 ++++++++++++++++++ ddtrace/profiling/collector/_memalloc_heap.c | 22 ++++++++++++++++ .../profiling/collector/_memalloc_reentrant.h | 26 +++++++++---------- ...getpid-from-memalloc-74f54043accdfc9e.yaml | 5 ++++ 4 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/profiling-remove-getpid-from-memalloc-74f54043accdfc9e.yaml diff --git a/ddtrace/profiling/collector/_memalloc.c b/ddtrace/profiling/collector/_memalloc.c index 3876517baaf..f3de61a7b2c 100644 --- a/ddtrace/profiling/collector/_memalloc.c +++ b/ddtrace/profiling/collector/_memalloc.c @@ -57,6 +57,28 @@ static alloc_tracker_t* global_alloc_tracker; static void memalloc_init(void); +static void +memalloc_prefork(void) +{ + // Lock the mutex prior to forking. This ensures that the memory profiler + // data structures will be in a consistent state in the child process. + // The rest of the memalloc calls do trylock so we don't run the risk + // of deadlocking if some other fork handler allocates + memlock_lock(&g_memalloc_lock); +} + +static void +memalloc_postfork_parent(void) +{ + memlock_unlock(&g_memalloc_lock); +} + +static void +memalloc_postfork_child(void) +{ + memlock_unlock(&g_memalloc_lock); +} + #ifdef _MSC_VER #pragma section(".CRT$XCU", read) __declspec(allocate(".CRT$XCU")) void (*memalloc_init_func)(void) = memalloc_init; @@ -81,6 +103,9 @@ memalloc_init() } } memlock_init(&g_memalloc_lock, crash_on_mutex_pass); +#ifndef _WIN32 + pthread_atfork(memalloc_prefork, memalloc_postfork_parent, memalloc_postfork_child); +#endif } static void diff --git a/ddtrace/profiling/collector/_memalloc_heap.c b/ddtrace/profiling/collector/_memalloc_heap.c index d2a5cc29eee..11e0d8dba8e 100644 --- a/ddtrace/profiling/collector/_memalloc_heap.c +++ b/ddtrace/profiling/collector/_memalloc_heap.c @@ -36,6 +36,25 @@ static heap_tracker_t global_heap_tracker; static void memheap_init(void); +static void +memheap_prefork(void) +{ + // See memalloc_prefork for an explanation of why this is here + memlock_lock(&g_memheap_lock); +} + +static void +memheap_postfork_parent(void) +{ + memlock_unlock(&g_memheap_lock); +} + +static void +memheap_postfork_child(void) +{ + memlock_unlock(&g_memheap_lock); +} + #ifdef _MSC_VER #pragma section(".CRT$XCU", read) __declspec(allocate(".CRT$XCU")) void (*memheap_init_func)(void) = memheap_init; @@ -60,6 +79,9 @@ memheap_init() } } memlock_init(&g_memheap_lock, crash_on_mutex_pass); +#ifndef _WIN32 + pthread_atfork(memheap_prefork, memheap_postfork_parent, memheap_postfork_child); +#endif } static uint32_t diff --git a/ddtrace/profiling/collector/_memalloc_reentrant.h b/ddtrace/profiling/collector/_memalloc_reentrant.h index cb4aa246961..54a07320236 100644 --- a/ddtrace/profiling/collector/_memalloc_reentrant.h +++ b/ddtrace/profiling/collector/_memalloc_reentrant.h @@ -125,19 +125,6 @@ memlock_trylock(memlock_t* lock) if (!lock) return false; -#ifdef __linux__ - // On Linux, we need to make sure we didn't just fork - // pthreads will guarantee the lock is consistent, but we at least need to clear it - static pid_t my_pid = 0; - if (my_pid == 0) { - my_pid = getpid(); - } else if (my_pid != getpid()) { - // We've forked, so we need to free the lock - memlock_unlock(lock); - my_pid = getpid(); - } -#endif - #ifdef _WIN32 bool result = WAIT_OBJECT_0 == WaitForSingleObject(lock->mutex, 0); // 0ms timeout -> no wait #else @@ -153,6 +140,19 @@ memlock_trylock(memlock_t* lock) return result; } +static inline void +memlock_lock(memlock_t* lock) +{ + if (!lock) + return; + +#ifdef _WIN32 + WaitForSingleObject(lock->mutex, INFINITE); +#else + pthread_mutex_lock(&lock->mutex); +#endif +} + // Cleanup function static inline bool memlock_destroy(memlock_t* lock) diff --git a/releasenotes/notes/profiling-remove-getpid-from-memalloc-74f54043accdfc9e.yaml b/releasenotes/notes/profiling-remove-getpid-from-memalloc-74f54043accdfc9e.yaml new file mode 100644 index 00000000000..1680dba0673 --- /dev/null +++ b/releasenotes/notes/profiling-remove-getpid-from-memalloc-74f54043accdfc9e.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + profiling: Removed a system call from the memory allocation profiler, used to detect forks, + which ran on every allocation and resulted in a significant slowdown. From 6ad41b12d369f2142f26c3293c9a2d223a9d6d89 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:26:42 +0100 Subject: [PATCH 371/372] chore(tracer): add core failsafe for missing span attached to context (#11734) Provide a failsafe option when accessing the span of a context, to avoid unexpected errors in case of an instrumentation failure that would not provide a span to the context. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/internal/core/__init__.py | 16 +++++++++------- ...e_context_span_failsafe-8b2f2f5344689c1d.yaml | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/core_context_span_failsafe-8b2f2f5344689c1d.yaml diff --git a/ddtrace/internal/core/__init__.py b/ddtrace/internal/core/__init__.py index fab9b07c183..da31218f73c 100644 --- a/ddtrace/internal/core/__init__.py +++ b/ddtrace/internal/core/__init__.py @@ -110,7 +110,6 @@ def _on_jsonify_context_started_flask(ctx): from typing import Dict # noqa:F401 from typing import List # noqa:F401 from typing import Optional # noqa:F401 -from typing import Union # noqa:F401 from ddtrace.vendor.debtcollector import deprecate @@ -276,7 +275,11 @@ def root(self): @property def span(self) -> "Span": if self._inner_span is None: - raise ValueError("No span set on ExecutionContext") + log.warning("No span found in ExecutionContext %s", self.identifier) + # failsafe + from ddtrace import tracer + + self._inner_span = tracer.current_span() or tracer.trace("default") return self._inner_span @span.setter @@ -362,15 +365,14 @@ def discard_local_item(data_key: str) -> None: def get_span() -> Optional["Span"]: current: Optional[ExecutionContext] = _CURRENT_CONTEXT.get() while current is not None: - try: - return current.span - except ValueError: - current = current.parent + if current._inner_span is not None: + return current._inner_span + current = current.parent return None def get_root_span() -> Optional["Span"]: - span = _CURRENT_CONTEXT.get().span + span = get_span() if span is None: return None return span._local_root or span diff --git a/releasenotes/notes/core_context_span_failsafe-8b2f2f5344689c1d.yaml b/releasenotes/notes/core_context_span_failsafe-8b2f2f5344689c1d.yaml new file mode 100644 index 00000000000..e4ef2651f64 --- /dev/null +++ b/releasenotes/notes/core_context_span_failsafe-8b2f2f5344689c1d.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + tracer: This fix resolves an issue where the core instrumentation could raise an uncaught exception. From 148da159e81e6f3b6cd0098904a1c3ce0c136bb1 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:27:25 -0800 Subject: [PATCH 372/372] ci: test a bunch of integrations against 3.13 (#11687) This change adds 3.13 to the list of Python versions tested against for many Riot environments. The ones that it leaves without 3.13 tests in place require more effort or specialized knowledge than the ones in this pull request did. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: brettlangdon Co-authored-by: Gabriele N. Tornetta Co-authored-by: Federico Mon Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Co-authored-by: Brett Langdon --- .riot/requirements/105b4fa.txt | 74 +++++++++++++ .../requirements/{108f9cf.txt => 1069d71.txt} | 26 ++--- .riot/requirements/1078c3b.txt | 27 +++++ .../requirements/{41b3223.txt => 108d1af.txt} | 24 ++--- .riot/requirements/109311c.txt | 22 ---- .riot/requirements/10f3c55.txt | 3 +- .../requirements/{13f7667.txt => 11b0a50.txt} | 22 ++-- .riot/requirements/1231d9a.txt | 20 ---- .riot/requirements/134deb1.txt | 27 +++++ .../requirements/{1b7df87.txt => 137d3ed.txt} | 6 +- .riot/requirements/1384411.txt | 26 +++++ .riot/requirements/13bb925.txt | 24 +++++ .../requirements/{22fdd8b.txt => 1486c11.txt} | 22 ++-- .../requirements/{9571147.txt => 14b883c.txt} | 10 +- .riot/requirements/14bdc60.txt | 24 ----- .riot/requirements/16054bb.txt | 20 ++++ .riot/requirements/161aef0.txt | 20 ++++ .riot/requirements/162f3c3.txt | 25 ----- .../requirements/{f823b38.txt => 164cf92.txt} | 18 ++-- .riot/requirements/167bd61.txt | 20 ++++ .riot/requirements/173a4e7.txt | 38 +++++++ .riot/requirements/173c90a.txt | 24 ----- .riot/requirements/176cb93.txt | 20 ---- .riot/requirements/1782179.txt | 20 ++++ .riot/requirements/1785987.txt | 22 ++++ .riot/requirements/178dbc8.txt | 23 ++++ .../requirements/{1f22277.txt => 17a7ba8.txt} | 10 +- .riot/requirements/1809449.txt | 27 ----- .riot/requirements/1842452.txt | 20 ++++ .riot/requirements/18730a4.txt | 20 ++++ .riot/requirements/18a1686.txt | 24 ----- .riot/requirements/197c6fd.txt | 22 ++++ .riot/requirements/19aab60.txt | 24 +++++ .riot/requirements/19db357.txt | 24 +++++ .../requirements/{1353f0b.txt => 19e4a89.txt} | 6 +- .../requirements/{1d3d3db.txt => 1a26314.txt} | 28 ++--- .riot/requirements/1a67f8a.txt | 22 ++++ .../requirements/{134c543.txt => 1b39725.txt} | 28 ++--- .riot/requirements/1b67e71.txt | 27 ----- .riot/requirements/1bb940b.txt | 24 ----- .riot/requirements/1bc2c36.txt | 23 ---- .riot/requirements/1c21210.txt | 24 +++++ .riot/requirements/1c5581b.txt | 3 +- .riot/requirements/1c756dc.txt | 22 ---- .riot/requirements/1cc7f49.txt | 28 ++--- .riot/requirements/1d15df5.txt | 22 ++++ .riot/requirements/1d44438.txt | 28 ++--- .riot/requirements/1d6049b.txt | 21 ++++ .riot/requirements/1db8cf2.txt | 28 +++++ .riot/requirements/1dd8c71.txt | 36 +++++++ .riot/requirements/1e0ee8d.txt | 20 ---- .riot/requirements/1e2c1f1.txt | 20 ++++ .riot/requirements/1e535fe.txt | 29 ----- .riot/requirements/1eef7c1.txt | 24 +++++ .riot/requirements/1efd2cc.txt | 24 ----- .../requirements/{16759fe.txt => 1f11fb6.txt} | 6 +- .riot/requirements/1f1570a.txt | 28 ----- .riot/requirements/1f1e236.txt | 20 ++++ .riot/requirements/1f8c44d.txt | 24 +++++ .riot/requirements/1f9dd35.txt | 20 ++++ .riot/requirements/2502b82.txt | 22 ++++ .riot/requirements/2b30eed.txt | 28 ----- .riot/requirements/37847ab.txt | 22 ---- .riot/requirements/38f510f.txt | 20 ++++ .riot/requirements/3f472ba.txt | 23 ++++ .../requirements/{71adece.txt => 4448684.txt} | 34 +++--- .riot/requirements/4de03a5.txt | 1 + .riot/requirements/4fe37f9.txt | 20 ++++ .riot/requirements/522a546.txt | 24 +++++ .riot/requirements/52e614f.txt | 28 +++++ .riot/requirements/562df6d.txt | 24 +++++ .riot/requirements/573ce40.txt | 26 ----- .riot/requirements/672002e.txt | 22 ++++ .../requirements/{fa3a84d.txt => 6b2bec6.txt} | 28 ++--- .riot/requirements/72c03ec.txt | 24 +++++ .../requirements/{1a692b1.txt => 840eb63.txt} | 8 +- .riot/requirements/8510e2e.txt | 22 ++++ .../requirements/{ee3bb72.txt => 883d27c.txt} | 32 +++--- .riot/requirements/8a19bdc.txt | 29 ----- .../requirements/{1322180.txt => 8b97b54.txt} | 26 ++--- .../requirements/{1933645.txt => 8ef50f6.txt} | 10 +- .riot/requirements/9093195.txt | 20 ---- .riot/requirements/9105e5c.txt | 24 ----- .riot/requirements/bc9aff8.txt | 25 +++++ .riot/requirements/bdada1a.txt | 24 +++++ .riot/requirements/c43afd8.txt | 20 ---- .riot/requirements/ccf18c9.txt | 20 ---- .riot/requirements/cdfce2e.txt | 20 ++++ .../requirements/{f709b80.txt => d8c9ddb.txt} | 18 ++-- .riot/requirements/dbcf3c6.txt | 24 +++++ .riot/requirements/dbdd8c0.txt | 24 ----- .../requirements/{5a5524a.txt => e312e0d.txt} | 4 +- .riot/requirements/edd1c7f.txt | 24 ----- .riot/requirements/eeaed0d.txt | 20 ++++ .riot/requirements/f066985.txt | 31 ------ .riot/requirements/f15bee1.txt | 24 +++++ .../requirements/{1912989.txt => f781048.txt} | 28 ++--- .riot/requirements/fb47988.txt | 32 +++--- .riot/requirements/fd74210.txt | 23 ---- ddtrace/internal/coverage/instrumentation.py | 4 +- .../coverage/instrumentation_py3_13.py | 23 ++++ hatch.toml | 2 +- pyproject.toml | 1 + ...-enable-integrations-01990085769ea3f3.yaml | 17 +++ riotfile.py | 101 +++++++++++++----- tests/contrib/falcon/app/resources.py | 14 ++- tests/contrib/kafka/test_kafka.py | 1 + 107 files changed, 1454 insertions(+), 950 deletions(-) create mode 100644 .riot/requirements/105b4fa.txt rename .riot/requirements/{108f9cf.txt => 1069d71.txt} (69%) create mode 100644 .riot/requirements/1078c3b.txt rename .riot/requirements/{41b3223.txt => 108d1af.txt} (75%) delete mode 100644 .riot/requirements/109311c.txt rename .riot/requirements/{13f7667.txt => 11b0a50.txt} (71%) delete mode 100644 .riot/requirements/1231d9a.txt create mode 100644 .riot/requirements/134deb1.txt rename .riot/requirements/{1b7df87.txt => 137d3ed.txt} (88%) create mode 100644 .riot/requirements/1384411.txt create mode 100644 .riot/requirements/13bb925.txt rename .riot/requirements/{22fdd8b.txt => 1486c11.txt} (71%) rename .riot/requirements/{9571147.txt => 14b883c.txt} (66%) delete mode 100644 .riot/requirements/14bdc60.txt create mode 100644 .riot/requirements/16054bb.txt create mode 100644 .riot/requirements/161aef0.txt delete mode 100644 .riot/requirements/162f3c3.txt rename .riot/requirements/{f823b38.txt => 164cf92.txt} (81%) create mode 100644 .riot/requirements/167bd61.txt create mode 100644 .riot/requirements/173a4e7.txt delete mode 100644 .riot/requirements/173c90a.txt delete mode 100644 .riot/requirements/176cb93.txt create mode 100644 .riot/requirements/1782179.txt create mode 100644 .riot/requirements/1785987.txt create mode 100644 .riot/requirements/178dbc8.txt rename .riot/requirements/{1f22277.txt => 17a7ba8.txt} (66%) delete mode 100644 .riot/requirements/1809449.txt create mode 100644 .riot/requirements/1842452.txt create mode 100644 .riot/requirements/18730a4.txt delete mode 100644 .riot/requirements/18a1686.txt create mode 100644 .riot/requirements/197c6fd.txt create mode 100644 .riot/requirements/19aab60.txt create mode 100644 .riot/requirements/19db357.txt rename .riot/requirements/{1353f0b.txt => 19e4a89.txt} (75%) rename .riot/requirements/{1d3d3db.txt => 1a26314.txt} (69%) create mode 100644 .riot/requirements/1a67f8a.txt rename .riot/requirements/{134c543.txt => 1b39725.txt} (66%) delete mode 100644 .riot/requirements/1b67e71.txt delete mode 100644 .riot/requirements/1bb940b.txt delete mode 100644 .riot/requirements/1bc2c36.txt create mode 100644 .riot/requirements/1c21210.txt delete mode 100644 .riot/requirements/1c756dc.txt create mode 100644 .riot/requirements/1d15df5.txt create mode 100644 .riot/requirements/1d6049b.txt create mode 100644 .riot/requirements/1db8cf2.txt create mode 100644 .riot/requirements/1dd8c71.txt delete mode 100644 .riot/requirements/1e0ee8d.txt create mode 100644 .riot/requirements/1e2c1f1.txt delete mode 100644 .riot/requirements/1e535fe.txt create mode 100644 .riot/requirements/1eef7c1.txt delete mode 100644 .riot/requirements/1efd2cc.txt rename .riot/requirements/{16759fe.txt => 1f11fb6.txt} (88%) delete mode 100644 .riot/requirements/1f1570a.txt create mode 100644 .riot/requirements/1f1e236.txt create mode 100644 .riot/requirements/1f8c44d.txt create mode 100644 .riot/requirements/1f9dd35.txt create mode 100644 .riot/requirements/2502b82.txt delete mode 100644 .riot/requirements/2b30eed.txt delete mode 100644 .riot/requirements/37847ab.txt create mode 100644 .riot/requirements/38f510f.txt create mode 100644 .riot/requirements/3f472ba.txt rename .riot/requirements/{71adece.txt => 4448684.txt} (64%) create mode 100644 .riot/requirements/4fe37f9.txt create mode 100644 .riot/requirements/522a546.txt create mode 100644 .riot/requirements/52e614f.txt create mode 100644 .riot/requirements/562df6d.txt delete mode 100644 .riot/requirements/573ce40.txt create mode 100644 .riot/requirements/672002e.txt rename .riot/requirements/{fa3a84d.txt => 6b2bec6.txt} (69%) create mode 100644 .riot/requirements/72c03ec.txt rename .riot/requirements/{1a692b1.txt => 840eb63.txt} (72%) create mode 100644 .riot/requirements/8510e2e.txt rename .riot/requirements/{ee3bb72.txt => 883d27c.txt} (63%) delete mode 100644 .riot/requirements/8a19bdc.txt rename .riot/requirements/{1322180.txt => 8b97b54.txt} (69%) rename .riot/requirements/{1933645.txt => 8ef50f6.txt} (66%) delete mode 100644 .riot/requirements/9093195.txt delete mode 100644 .riot/requirements/9105e5c.txt create mode 100644 .riot/requirements/bc9aff8.txt create mode 100644 .riot/requirements/bdada1a.txt delete mode 100644 .riot/requirements/c43afd8.txt delete mode 100644 .riot/requirements/ccf18c9.txt create mode 100644 .riot/requirements/cdfce2e.txt rename .riot/requirements/{f709b80.txt => d8c9ddb.txt} (81%) create mode 100644 .riot/requirements/dbcf3c6.txt delete mode 100644 .riot/requirements/dbdd8c0.txt rename .riot/requirements/{5a5524a.txt => e312e0d.txt} (90%) delete mode 100644 .riot/requirements/edd1c7f.txt create mode 100644 .riot/requirements/eeaed0d.txt delete mode 100644 .riot/requirements/f066985.txt create mode 100644 .riot/requirements/f15bee1.txt rename .riot/requirements/{1912989.txt => f781048.txt} (66%) delete mode 100644 .riot/requirements/fd74210.txt create mode 100644 ddtrace/internal/coverage/instrumentation_py3_13.py create mode 100644 releasenotes/notes/313-enable-integrations-01990085769ea3f3.yaml diff --git a/.riot/requirements/105b4fa.txt b/.riot/requirements/105b4fa.txt new file mode 100644 index 00000000000..cd828ea969c --- /dev/null +++ b/.riot/requirements/105b4fa.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/105b4fa.in +# +arrow==1.3.0 +asgiref==3.8.1 +attrs==24.3.0 +autobahn==24.4.2 +automat==24.8.1 +bcrypt==4.2.1 +blessed==1.20.0 +certifi==2024.12.14 +cffi==1.17.1 +channels==4.2.0 +charset-normalizer==3.4.0 +constantly==23.10.4 +coverage[toml]==7.6.9 +cryptography==44.0.0 +daphne==4.1.2 +django==4.2.17 +django-configurations==2.5.1 +django-picklefield==3.2 +django-pylibmc==0.6.1 +django-q==1.3.6 +django-redis==4.5.0 +hyperlink==21.0.0 +hypothesis==6.45.0 +idna==3.10 +incremental==24.7.2 +iniconfig==2.0.0 +isodate==0.7.2 +lxml==5.3.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +platformdirs==4.3.6 +pluggy==1.5.0 +psycopg==3.2.3 +psycopg2-binary==2.9.10 +pyasn1==0.6.1 +pyasn1-modules==0.4.1 +pycparser==2.22 +pylibmc==1.6.3 +pyopenssl==24.3.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-dateutil==2.9.0.post0 +python-memcached==1.62 +pytz==2024.2 +redis==2.10.6 +requests==2.32.3 +requests-file==2.1.0 +requests-toolbelt==1.0.0 +service-identity==24.2.0 +six==1.17.0 +sortedcontainers==2.4.0 +spyne==2.14.0 +sqlparse==0.5.3 +twisted[tls]==24.11.0 +txaio==23.1.1 +types-python-dateutil==2.9.0.20241206 +typing-extensions==4.12.2 +urllib3==2.3.0 +wcwidth==0.2.13 +zeep==4.3.1 +zope-interface==7.2 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.6.0 diff --git a/.riot/requirements/108f9cf.txt b/.riot/requirements/1069d71.txt similarity index 69% rename from .riot/requirements/108f9cf.txt rename to .riot/requirements/1069d71.txt index 68e407c1fea..d528654c86a 100644 --- a/.riot/requirements/108f9cf.txt +++ b/.riot/requirements/1069d71.txt @@ -2,37 +2,37 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/108f9cf.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1069d71.in # aiofiles==24.1.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1078c3b.txt b/.riot/requirements/1078c3b.txt new file mode 100644 index 00000000000..00d9778e6da --- /dev/null +++ b/.riot/requirements/1078c3b.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1078c3b.in +# +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.0.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +sqlalchemy==1.3.24 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/41b3223.txt b/.riot/requirements/108d1af.txt similarity index 75% rename from .riot/requirements/41b3223.txt rename to .riot/requirements/108d1af.txt index d97afcad0ae..3afb1286ce6 100644 --- a/.riot/requirements/41b3223.txt +++ b/.riot/requirements/108d1af.txt @@ -2,19 +2,19 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/41b3223.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/108d1af.in # aiofiles==24.1.0 annotated-types==0.7.0 anyio==4.5.2 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 -fastapi==0.115.2 +fastapi==0.115.6 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -22,21 +22,21 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.12 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.40.0 -tomli==2.0.2 +starlette==0.41.3 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/109311c.txt b/.riot/requirements/109311c.txt deleted file mode 100644 index be68727bd91..00000000000 --- a/.riot/requirements/109311c.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/109311c.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.0.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/10f3c55.txt b/.riot/requirements/10f3c55.txt index 34a8e65f917..95237c5566b 100644 --- a/.riot/requirements/10f3c55.txt +++ b/.riot/requirements/10f3c55.txt @@ -17,7 +17,7 @@ channels==4.2.0 charset-normalizer==3.4.0 constantly==23.10.4 coverage[toml]==7.6.9 -cryptography==43.0.3 +cryptography==44.0.0 daphne==4.1.2 django==4.2.17 django-configurations==2.5.1 @@ -39,7 +39,6 @@ opentracing==2.4.0 packaging==24.2 platformdirs==4.3.6 pluggy==1.5.0 -psycopg==3.2.3 psycopg2-binary==2.9.10 pyasn1==0.6.1 pyasn1-modules==0.4.1 diff --git a/.riot/requirements/13f7667.txt b/.riot/requirements/11b0a50.txt similarity index 71% rename from .riot/requirements/13f7667.txt rename to .riot/requirements/11b0a50.txt index cdb3642f7bf..e7ae1d3b05b 100644 --- a/.riot/requirements/13f7667.txt +++ b/.riot/requirements/11b0a50.txt @@ -2,32 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13f7667.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/11b0a50.in # aiofiles==24.1.0 anyio==3.7.1 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 fastapi==0.86.0 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1231d9a.txt b/.riot/requirements/1231d9a.txt deleted file mode 100644 index 15a480253de..00000000000 --- a/.riot/requirements/1231d9a.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1231d9a.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/134deb1.txt b/.riot/requirements/134deb1.txt new file mode 100644 index 00000000000..71a346694a6 --- /dev/null +++ b/.riot/requirements/134deb1.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/134deb1.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==1.3.24 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1b7df87.txt b/.riot/requirements/137d3ed.txt similarity index 88% rename from .riot/requirements/1b7df87.txt rename to .riot/requirements/137d3ed.txt index b23492441b9..72b5cba9c65 100644 --- a/.riot/requirements/1b7df87.txt +++ b/.riot/requirements/137d3ed.txt @@ -2,12 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1b7df87.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/137d3ed.in # aiofiles==23.2.1 anyio==3.7.1 attrs==24.2.0 -certifi==2024.8.30 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.0 pluggy==1.2.0 -pydantic==1.10.18 +pydantic==1.10.19 pytest==7.4.4 pytest-asyncio==0.21.1 pytest-cov==4.1.0 diff --git a/.riot/requirements/1384411.txt b/.riot/requirements/1384411.txt new file mode 100644 index 00000000000..6cf47ca63ad --- /dev/null +++ b/.riot/requirements/1384411.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1384411.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tomli==2.2.1 +typing-extensions==4.12.2 diff --git a/.riot/requirements/13bb925.txt b/.riot/requirements/13bb925.txt new file mode 100644 index 00000000000..f87641d20cc --- /dev/null +++ b/.riot/requirements/13bb925.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13bb925.in +# +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +falcon==3.0.1 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/22fdd8b.txt b/.riot/requirements/1486c11.txt similarity index 71% rename from .riot/requirements/22fdd8b.txt rename to .riot/requirements/1486c11.txt index c2ab379f101..a3cd0d8268d 100644 --- a/.riot/requirements/22fdd8b.txt +++ b/.riot/requirements/1486c11.txt @@ -2,32 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/22fdd8b.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1486c11.in # aiofiles==24.1.0 anyio==3.7.1 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 fastapi==0.86.0 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/9571147.txt b/.riot/requirements/14b883c.txt similarity index 66% rename from .riot/requirements/9571147.txt rename to .riot/requirements/14b883c.txt index 37c6cbc9ad9..fc5fce6885f 100644 --- a/.riot/requirements/9571147.txt +++ b/.riot/requirements/14b883c.txt @@ -2,20 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/9571147.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/14b883c.in # -attrs==23.1.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 falcon==3.1.3 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 -pytest==7.4.3 +pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/14bdc60.txt b/.riot/requirements/14bdc60.txt deleted file mode 100644 index b911301abde..00000000000 --- a/.riot/requirements/14bdc60.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/14bdc60.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/16054bb.txt b/.riot/requirements/16054bb.txt new file mode 100644 index 00000000000..df2a3b259cd --- /dev/null +++ b/.riot/requirements/16054bb.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16054bb.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==3.0.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/161aef0.txt b/.riot/requirements/161aef0.txt new file mode 100644 index 00000000000..dad819d4d9c --- /dev/null +++ b/.riot/requirements/161aef0.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/161aef0.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==4.0.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/162f3c3.txt b/.riot/requirements/162f3c3.txt deleted file mode 100644 index 05535f25438..00000000000 --- a/.riot/requirements/162f3c3.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/162f3c3.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 -tomli==2.0.1 diff --git a/.riot/requirements/f823b38.txt b/.riot/requirements/164cf92.txt similarity index 81% rename from .riot/requirements/f823b38.txt rename to .riot/requirements/164cf92.txt index 99549b8b77f..3fd752de5e8 100644 --- a/.riot/requirements/f823b38.txt +++ b/.riot/requirements/164cf92.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f823b38.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/164cf92.in # aiofiles==24.1.0 anyio==4.5.2 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -21,20 +21,20 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.12 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/167bd61.txt b/.riot/requirements/167bd61.txt new file mode 100644 index 00000000000..da0880effad --- /dev/null +++ b/.riot/requirements/167bd61.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/167bd61.in +# +attrs==24.3.0 +confluent-kafka==2.6.2 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/173a4e7.txt b/.riot/requirements/173a4e7.txt new file mode 100644 index 00000000000..8402a7e1aad --- /dev/null +++ b/.riot/requirements/173a4e7.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/173a4e7.in +# +aiofiles==24.1.0 +annotated-types==0.7.0 +anyio==3.7.1 +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.0 +coverage[toml]==7.6.9 +fastapi==0.115.6 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==2.10.4 +pydantic-core==2.27.2 +pytest==8.3.4 +pytest-asyncio==0.21.1 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-multipart==0.0.20 +requests==2.32.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.41.3 +typing-extensions==4.12.2 +urllib3==2.2.3 diff --git a/.riot/requirements/173c90a.txt b/.riot/requirements/173c90a.txt deleted file mode 100644 index 4e9e47e9130..00000000000 --- a/.riot/requirements/173c90a.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/173c90a.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.0.1 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/176cb93.txt b/.riot/requirements/176cb93.txt deleted file mode 100644 index 6259626b1f4..00000000000 --- a/.riot/requirements/176cb93.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/176cb93.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.0.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1782179.txt b/.riot/requirements/1782179.txt new file mode 100644 index 00000000000..b6e578ca240 --- /dev/null +++ b/.riot/requirements/1782179.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1782179.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==3.0.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1785987.txt b/.riot/requirements/1785987.txt new file mode 100644 index 00000000000..eee10d642e6 --- /dev/null +++ b/.riot/requirements/1785987.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1785987.in +# +attrs==24.2.0 +coverage[toml]==7.6.9 +googleapis-common-protos==1.66.0 +grpcio==1.66.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +protobuf==5.29.1 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/178dbc8.txt b/.riot/requirements/178dbc8.txt new file mode 100644 index 00000000000..ef5d1cf72c2 --- /dev/null +++ b/.riot/requirements/178dbc8.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/178dbc8.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==1.3.24 diff --git a/.riot/requirements/1f22277.txt b/.riot/requirements/17a7ba8.txt similarity index 66% rename from .riot/requirements/1f22277.txt rename to .riot/requirements/17a7ba8.txt index f8018be64b4..7984fff1ed6 100644 --- a/.riot/requirements/1f22277.txt +++ b/.riot/requirements/17a7ba8.txt @@ -2,20 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1f22277.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/17a7ba8.in # -attrs==23.1.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 falcon==3.1.3 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 -pytest==7.4.3 +pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/1809449.txt b/.riot/requirements/1809449.txt deleted file mode 100644 index a874c5c1994..00000000000 --- a/.riot/requirements/1809449.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1809449.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==7.1.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 -tomli==2.0.1 -zipp==3.18.1 diff --git a/.riot/requirements/1842452.txt b/.riot/requirements/1842452.txt new file mode 100644 index 00000000000..2c13c9135fd --- /dev/null +++ b/.riot/requirements/1842452.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1842452.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==4.0.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/18730a4.txt b/.riot/requirements/18730a4.txt new file mode 100644 index 00000000000..20d9f1d8a90 --- /dev/null +++ b/.riot/requirements/18730a4.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18730a4.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysqlclient==2.2.6 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/18a1686.txt b/.riot/requirements/18a1686.txt deleted file mode 100644 index 9ba614bfc37..00000000000 --- a/.riot/requirements/18a1686.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/18a1686.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/197c6fd.txt b/.riot/requirements/197c6fd.txt new file mode 100644 index 00000000000..745e2a9df4d --- /dev/null +++ b/.riot/requirements/197c6fd.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/197c6fd.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==3.1.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 diff --git a/.riot/requirements/19aab60.txt b/.riot/requirements/19aab60.txt new file mode 100644 index 00000000000..0bf2d25d3a2 --- /dev/null +++ b/.riot/requirements/19aab60.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/19aab60.in +# +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +falcon==4.0.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/19db357.txt b/.riot/requirements/19db357.txt new file mode 100644 index 00000000000..1e3ddd64be4 --- /dev/null +++ b/.riot/requirements/19db357.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/19db357.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/1353f0b.txt b/.riot/requirements/19e4a89.txt similarity index 75% rename from .riot/requirements/1353f0b.txt rename to .riot/requirements/19e4a89.txt index c26716cca4b..a43fb84b853 100644 --- a/.riot/requirements/1353f0b.txt +++ b/.riot/requirements/19e4a89.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1353f0b.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/19e4a89.in # -attrs==23.2.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 greenlet==3.0.3 hypothesis==6.45.0 importlib-metadata==6.7.0 diff --git a/.riot/requirements/1d3d3db.txt b/.riot/requirements/1a26314.txt similarity index 69% rename from .riot/requirements/1d3d3db.txt rename to .riot/requirements/1a26314.txt index 8e4d720320e..7c381d39d24 100644 --- a/.riot/requirements/1d3d3db.txt +++ b/.riot/requirements/1a26314.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d3d3db.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a26314.in # aiofiles==24.1.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -21,20 +21,20 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/1a67f8a.txt b/.riot/requirements/1a67f8a.txt new file mode 100644 index 00000000000..19b9a07ff78 --- /dev/null +++ b/.riot/requirements/1a67f8a.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1a67f8a.in +# +aiomysql==0.1.1 +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pymysql==1.1.1 +pytest==8.3.4 +pytest-asyncio==0.25.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/134c543.txt b/.riot/requirements/1b39725.txt similarity index 66% rename from .riot/requirements/134c543.txt rename to .riot/requirements/1b39725.txt index 9b47784aff9..ac78a5259be 100644 --- a/.riot/requirements/134c543.txt +++ b/.riot/requirements/1b39725.txt @@ -2,37 +2,37 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/134c543.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b39725.in # aiofiles==24.1.0 annotated-types==0.7.0 anyio==3.7.1 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 -fastapi==0.115.2 +coverage[toml]==7.6.9 +fastapi==0.115.6 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.40.0 +starlette==0.41.3 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1b67e71.txt b/.riot/requirements/1b67e71.txt deleted file mode 100644 index 25a2a3e0ad0..00000000000 --- a/.riot/requirements/1b67e71.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1b67e71.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==7.1.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 -tomli==2.0.1 -zipp==3.18.1 diff --git a/.riot/requirements/1bb940b.txt b/.riot/requirements/1bb940b.txt deleted file mode 100644 index b4d6c0de42e..00000000000 --- a/.riot/requirements/1bb940b.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1bb940b.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.0.1 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1bc2c36.txt b/.riot/requirements/1bc2c36.txt deleted file mode 100644 index c2c1d4bb8ab..00000000000 --- a/.riot/requirements/1bc2c36.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1bc2c36.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 diff --git a/.riot/requirements/1c21210.txt b/.riot/requirements/1c21210.txt new file mode 100644 index 00000000000..e8b03b2b581 --- /dev/null +++ b/.riot/requirements/1c21210.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c21210.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==3.1.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1c5581b.txt b/.riot/requirements/1c5581b.txt index 4886bf012bf..6b1f227cb15 100644 --- a/.riot/requirements/1c5581b.txt +++ b/.riot/requirements/1c5581b.txt @@ -17,7 +17,7 @@ channels==4.2.0 charset-normalizer==3.4.0 constantly==23.10.4 coverage[toml]==7.6.9 -cryptography==43.0.3 +cryptography==44.0.0 daphne==4.1.2 django==4.2.17 django-configurations==2.5.1 @@ -39,6 +39,7 @@ opentracing==2.4.0 packaging==24.2 platformdirs==4.3.6 pluggy==1.5.0 +psycopg==3.2.3 psycopg2-binary==2.9.10 pyasn1==0.6.1 pyasn1-modules==0.4.1 diff --git a/.riot/requirements/1c756dc.txt b/.riot/requirements/1c756dc.txt deleted file mode 100644 index 06878afcd04..00000000000 --- a/.riot/requirements/1c756dc.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1c756dc.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/1cc7f49.txt b/.riot/requirements/1cc7f49.txt index 4c33ca5b861..644fd59d910 100644 --- a/.riot/requirements/1cc7f49.txt +++ b/.riot/requirements/1cc7f49.txt @@ -4,26 +4,26 @@ # # pip-compile --no-annotate .riot/requirements/1cc7f49.in # -attrs==23.2.0 -coverage[toml]==7.4.1 +attrs==24.3.0 +coverage[toml]==7.6.9 glob2==0.7 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.1.0 opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 +packaging==24.2 +parse==1.20.2 +parse-type==0.6.4 +pluggy==1.5.0 py==1.11.0 -pytest==8.0.0 +pytest==8.3.4 pytest-bdd==6.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +six==1.17.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d15df5.txt b/.riot/requirements/1d15df5.txt new file mode 100644 index 00000000000..7aa37dc4bff --- /dev/null +++ b/.riot/requirements/1d15df5.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d15df5.in +# +attrs==24.2.0 +coverage[toml]==7.6.9 +googleapis-common-protos==1.66.0 +grpcio==1.68.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +protobuf==5.29.1 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d44438.txt b/.riot/requirements/1d44438.txt index c067e47f7a9..5a2fbefacd8 100644 --- a/.riot/requirements/1d44438.txt +++ b/.riot/requirements/1d44438.txt @@ -4,26 +4,26 @@ # # pip-compile --no-annotate .riot/requirements/1d44438.in # -attrs==23.2.0 -coverage[toml]==7.4.1 +attrs==24.3.0 +coverage[toml]==7.6.9 glob2==0.7 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.1.0 opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 +packaging==24.2 +parse==1.20.2 +parse-type==0.6.4 +pluggy==1.5.0 py==1.11.0 -pytest==8.0.0 +pytest==8.3.4 pytest-bdd==6.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +six==1.17.0 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d6049b.txt b/.riot/requirements/1d6049b.txt new file mode 100644 index 00000000000..bd72dc72359 --- /dev/null +++ b/.riot/requirements/1d6049b.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1d6049b.in +# +asyncpg==0.30.0 +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.21.2 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1db8cf2.txt b/.riot/requirements/1db8cf2.txt new file mode 100644 index 00000000000..5cf71f8f771 --- /dev/null +++ b/.riot/requirements/1db8cf2.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1db8cf2.in +# +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.0.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tomli==2.2.1 +typing-extensions==4.12.2 +zipp==3.20.2 diff --git a/.riot/requirements/1dd8c71.txt b/.riot/requirements/1dd8c71.txt new file mode 100644 index 00000000000..569f278cbc6 --- /dev/null +++ b/.riot/requirements/1dd8c71.txt @@ -0,0 +1,36 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1dd8c71.in +# +aiofiles==24.1.0 +anyio==3.7.1 +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.0 +coverage[toml]==7.6.9 +fastapi==0.86.0 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pydantic==1.10.19 +pytest==8.3.4 +pytest-asyncio==0.21.1 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +python-multipart==0.0.20 +requests==2.32.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.20.4 +typing-extensions==4.12.2 +urllib3==2.2.3 diff --git a/.riot/requirements/1e0ee8d.txt b/.riot/requirements/1e0ee8d.txt deleted file mode 100644 index f18af75a99a..00000000000 --- a/.riot/requirements/1e0ee8d.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1e0ee8d.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.0.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e2c1f1.txt b/.riot/requirements/1e2c1f1.txt new file mode 100644 index 00000000000..ccd4918ea54 --- /dev/null +++ b/.riot/requirements/1e2c1f1.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e2c1f1.in +# +attrs==24.3.0 +avro==1.12.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e535fe.txt b/.riot/requirements/1e535fe.txt deleted file mode 100644 index f04e722a5ff..00000000000 --- a/.riot/requirements/1e535fe.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1e535fe.in -# -attrs==23.2.0 -coverage[toml]==7.4.1 -glob2==0.7 -hypothesis==6.45.0 -iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 -mock==5.1.0 -more-itertools==8.10.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 -py==1.11.0 -pytest==8.0.0 -pytest-bdd==4.1.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1eef7c1.txt b/.riot/requirements/1eef7c1.txt new file mode 100644 index 00000000000..331cab10062 --- /dev/null +++ b/.riot/requirements/1eef7c1.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1eef7c1.in +# +aiopg==1.4.0 +async-timeout==4.0.3 +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/1efd2cc.txt b/.riot/requirements/1efd2cc.txt deleted file mode 100644 index 7d049eba04a..00000000000 --- a/.riot/requirements/1efd2cc.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1efd2cc.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.28 -typing-extensions==4.10.0 diff --git a/.riot/requirements/16759fe.txt b/.riot/requirements/1f11fb6.txt similarity index 88% rename from .riot/requirements/16759fe.txt rename to .riot/requirements/1f11fb6.txt index 27b3dd8d79e..3dd008ccf49 100644 --- a/.riot/requirements/16759fe.txt +++ b/.riot/requirements/1f11fb6.txt @@ -2,12 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/16759fe.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1f11fb6.in # aiofiles==23.2.1 anyio==3.7.1 attrs==24.2.0 -certifi==2024.8.30 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 @@ -23,7 +23,7 @@ mock==5.1.0 opentracing==2.4.0 packaging==24.0 pluggy==1.2.0 -pydantic==1.10.18 +pydantic==1.10.19 pytest==7.4.4 pytest-asyncio==0.21.1 pytest-cov==4.1.0 diff --git a/.riot/requirements/1f1570a.txt b/.riot/requirements/1f1570a.txt deleted file mode 100644 index e1cf44a2660..00000000000 --- a/.riot/requirements/1f1570a.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1f1570a.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==7.1.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.28 -tomli==2.0.1 -typing-extensions==4.10.0 -zipp==3.18.1 diff --git a/.riot/requirements/1f1e236.txt b/.riot/requirements/1f1e236.txt new file mode 100644 index 00000000000..6b89eac7d7e --- /dev/null +++ b/.riot/requirements/1f1e236.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f1e236.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==4.0.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1f8c44d.txt b/.riot/requirements/1f8c44d.txt new file mode 100644 index 00000000000..67857363013 --- /dev/null +++ b/.riot/requirements/1f8c44d.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f8c44d.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/1f9dd35.txt b/.riot/requirements/1f9dd35.txt new file mode 100644 index 00000000000..63225143f57 --- /dev/null +++ b/.riot/requirements/1f9dd35.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f9dd35.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==3.1.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/2502b82.txt b/.riot/requirements/2502b82.txt new file mode 100644 index 00000000000..521082afb6d --- /dev/null +++ b/.riot/requirements/2502b82.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/2502b82.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==4.0.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 diff --git a/.riot/requirements/2b30eed.txt b/.riot/requirements/2b30eed.txt deleted file mode 100644 index 4666128969f..00000000000 --- a/.riot/requirements/2b30eed.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/2b30eed.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==7.1.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.28 -tomli==2.0.1 -typing-extensions==4.10.0 -zipp==3.18.1 diff --git a/.riot/requirements/37847ab.txt b/.riot/requirements/37847ab.txt deleted file mode 100644 index 35ecea5e3c7..00000000000 --- a/.riot/requirements/37847ab.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/37847ab.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/38f510f.txt b/.riot/requirements/38f510f.txt new file mode 100644 index 00000000000..17fae77250d --- /dev/null +++ b/.riot/requirements/38f510f.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/38f510f.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==4.0.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/3f472ba.txt b/.riot/requirements/3f472ba.txt new file mode 100644 index 00000000000..0e303a01101 --- /dev/null +++ b/.riot/requirements/3f472ba.txt @@ -0,0 +1,23 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/3f472ba.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==1.3.24 diff --git a/.riot/requirements/71adece.txt b/.riot/requirements/4448684.txt similarity index 64% rename from .riot/requirements/71adece.txt rename to .riot/requirements/4448684.txt index 3c7da46fac6..225d852c80b 100644 --- a/.riot/requirements/71adece.txt +++ b/.riot/requirements/4448684.txt @@ -2,19 +2,19 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/71adece.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4448684.in # aiofiles==24.1.0 annotated-types==0.7.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 -fastapi==0.115.2 +fastapi==0.115.6 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -22,21 +22,21 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.40.0 -tomli==2.0.2 +starlette==0.41.3 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/4de03a5.txt b/.riot/requirements/4de03a5.txt index 5f1cc3a70e4..8fa32aa29f3 100644 --- a/.riot/requirements/4de03a5.txt +++ b/.riot/requirements/4de03a5.txt @@ -40,6 +40,7 @@ opentracing==2.4.0 packaging==24.2 platformdirs==4.3.6 pluggy==1.5.0 +psycopg==3.2.3 psycopg2-binary==2.9.10 pyasn1==0.6.1 pyasn1-modules==0.4.1 diff --git a/.riot/requirements/4fe37f9.txt b/.riot/requirements/4fe37f9.txt new file mode 100644 index 00000000000..d23097a4f9f --- /dev/null +++ b/.riot/requirements/4fe37f9.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4fe37f9.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +protobuf==5.29.1 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/522a546.txt b/.riot/requirements/522a546.txt new file mode 100644 index 00000000000..9fde2ac3f0d --- /dev/null +++ b/.riot/requirements/522a546.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/522a546.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==3.0.1 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/52e614f.txt b/.riot/requirements/52e614f.txt new file mode 100644 index 00000000000..4d72e7c6c5c --- /dev/null +++ b/.riot/requirements/52e614f.txt @@ -0,0 +1,28 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/52e614f.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +tomli==2.2.1 +typing-extensions==4.12.2 +zipp==3.21.0 diff --git a/.riot/requirements/562df6d.txt b/.riot/requirements/562df6d.txt new file mode 100644 index 00000000000..2204cad14fa --- /dev/null +++ b/.riot/requirements/562df6d.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/562df6d.in +# +aiopg==1.4.0 +async-timeout==4.0.3 +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/573ce40.txt b/.riot/requirements/573ce40.txt deleted file mode 100644 index ccbcbd82b2b..00000000000 --- a/.riot/requirements/573ce40.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/573ce40.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.28 -tomli==2.0.1 -typing-extensions==4.10.0 diff --git a/.riot/requirements/672002e.txt b/.riot/requirements/672002e.txt new file mode 100644 index 00000000000..d39815f738c --- /dev/null +++ b/.riot/requirements/672002e.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/672002e.in +# +aiomysql==0.2.0 +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pymysql==1.1.1 +pytest==8.3.4 +pytest-asyncio==0.25.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/fa3a84d.txt b/.riot/requirements/6b2bec6.txt similarity index 69% rename from .riot/requirements/fa3a84d.txt rename to .riot/requirements/6b2bec6.txt index c8259b6ba0d..f7bfa544703 100644 --- a/.riot/requirements/fa3a84d.txt +++ b/.riot/requirements/6b2bec6.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/fa3a84d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6b2bec6.in # aiofiles==24.1.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -21,20 +21,20 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/.riot/requirements/72c03ec.txt b/.riot/requirements/72c03ec.txt new file mode 100644 index 00000000000..1f8978ea899 --- /dev/null +++ b/.riot/requirements/72c03ec.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/72c03ec.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==4.0.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.21.0 diff --git a/.riot/requirements/1a692b1.txt b/.riot/requirements/840eb63.txt similarity index 72% rename from .riot/requirements/1a692b1.txt rename to .riot/requirements/840eb63.txt index a13b449b62b..431128fcb61 100644 --- a/.riot/requirements/1a692b1.txt +++ b/.riot/requirements/840eb63.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1a692b1.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/840eb63.in # -attrs==23.2.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 greenlet==3.0.3 hypothesis==6.45.0 importlib-metadata==6.7.0 @@ -23,7 +23,7 @@ pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 sortedcontainers==2.4.0 -sqlalchemy==2.0.28 +sqlalchemy==2.0.36 tomli==2.0.1 typing-extensions==4.7.1 zipp==3.15.0 diff --git a/.riot/requirements/8510e2e.txt b/.riot/requirements/8510e2e.txt new file mode 100644 index 00000000000..b03829a2828 --- /dev/null +++ b/.riot/requirements/8510e2e.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/8510e2e.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +falcon==3.0.1 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 diff --git a/.riot/requirements/ee3bb72.txt b/.riot/requirements/883d27c.txt similarity index 63% rename from .riot/requirements/ee3bb72.txt rename to .riot/requirements/883d27c.txt index 9ba70beec7b..4e9f25badee 100644 --- a/.riot/requirements/ee3bb72.txt +++ b/.riot/requirements/883d27c.txt @@ -2,39 +2,39 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ee3bb72.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/883d27c.in # aiofiles==24.1.0 annotated-types==0.7.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 -fastapi==0.115.2 +fastapi==0.115.6 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.40.0 -tomli==2.0.2 +starlette==0.41.3 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/8a19bdc.txt b/.riot/requirements/8a19bdc.txt deleted file mode 100644 index 4561c44afa0..00000000000 --- a/.riot/requirements/8a19bdc.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/8a19bdc.in -# -attrs==23.2.0 -coverage[toml]==7.4.1 -glob2==0.7 -hypothesis==6.45.0 -iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 -mock==5.1.0 -more-itertools==8.10.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 -py==1.11.0 -pytest==8.0.0 -pytest-bdd==4.1.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1322180.txt b/.riot/requirements/8b97b54.txt similarity index 69% rename from .riot/requirements/1322180.txt rename to .riot/requirements/8b97b54.txt index d7f77ff6d04..82f79372afe 100644 --- a/.riot/requirements/1322180.txt +++ b/.riot/requirements/8b97b54.txt @@ -2,37 +2,37 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1322180.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/8b97b54.in # aiofiles==24.1.0 -anyio==4.6.2.post1 -attrs==24.2.0 -certifi==2024.8.30 +anyio==4.7.0 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 +coverage[toml]==7.6.9 exceptiongroup==1.2.2 fastapi==0.64.0 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.13.6 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/1933645.txt b/.riot/requirements/8ef50f6.txt similarity index 66% rename from .riot/requirements/1933645.txt rename to .riot/requirements/8ef50f6.txt index 5e14a046e99..f3a48228267 100644 --- a/.riot/requirements/1933645.txt +++ b/.riot/requirements/8ef50f6.txt @@ -2,20 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1933645.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/8ef50f6.in # -attrs==23.1.0 +attrs==24.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 falcon==3.0.1 hypothesis==6.45.0 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 -pytest==7.4.3 +pytest==7.4.4 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 diff --git a/.riot/requirements/9093195.txt b/.riot/requirements/9093195.txt deleted file mode 100644 index 1d127a8d540..00000000000 --- a/.riot/requirements/9093195.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/9093195.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/9105e5c.txt b/.riot/requirements/9105e5c.txt deleted file mode 100644 index 0e89e7c376d..00000000000 --- a/.riot/requirements/9105e5c.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/9105e5c.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/bc9aff8.txt b/.riot/requirements/bc9aff8.txt new file mode 100644 index 00000000000..25b83e2618d --- /dev/null +++ b/.riot/requirements/bc9aff8.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bc9aff8.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 +greenlet==3.0.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==1.3.24 +tomli==2.2.1 diff --git a/.riot/requirements/bdada1a.txt b/.riot/requirements/bdada1a.txt new file mode 100644 index 00000000000..2a394359c49 --- /dev/null +++ b/.riot/requirements/bdada1a.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bdada1a.in +# +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +falcon==3.1.3 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +zipp==3.20.2 diff --git a/.riot/requirements/c43afd8.txt b/.riot/requirements/c43afd8.txt deleted file mode 100644 index abca1fbd489..00000000000 --- a/.riot/requirements/c43afd8.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/c43afd8.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/ccf18c9.txt b/.riot/requirements/ccf18c9.txt deleted file mode 100644 index ce270ef4272..00000000000 --- a/.riot/requirements/ccf18c9.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/ccf18c9.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -falcon==3.1.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/cdfce2e.txt b/.riot/requirements/cdfce2e.txt new file mode 100644 index 00000000000..1b93f8e5a0e --- /dev/null +++ b/.riot/requirements/cdfce2e.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/cdfce2e.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +falcon==3.1.3 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/f709b80.txt b/.riot/requirements/d8c9ddb.txt similarity index 81% rename from .riot/requirements/f709b80.txt rename to .riot/requirements/d8c9ddb.txt index 4d8eec013bb..3d33b05725f 100644 --- a/.riot/requirements/f709b80.txt +++ b/.riot/requirements/d8c9ddb.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f709b80.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/d8c9ddb.in # aiofiles==24.1.0 anyio==4.5.2 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.6.1 exceptiongroup==1.2.2 fastapi==0.90.1 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 @@ -21,20 +21,20 @@ importlib-metadata==8.5.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==1.10.18 -pytest==8.3.3 +pydantic==1.10.19 +pytest==8.3.4 pytest-asyncio==0.21.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-randomly==3.15.0 -python-multipart==0.0.12 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.23.1 -tomli==2.0.2 +tomli==2.2.1 typing-extensions==4.12.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/.riot/requirements/dbcf3c6.txt b/.riot/requirements/dbcf3c6.txt new file mode 100644 index 00000000000..40184c2cbde --- /dev/null +++ b/.riot/requirements/dbcf3c6.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/dbcf3c6.in +# +attrs==24.2.0 +coverage[toml]==7.6.9 +greenlet==3.1.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/dbdd8c0.txt b/.riot/requirements/dbdd8c0.txt deleted file mode 100644 index 2b366f72c7d..00000000000 --- a/.riot/requirements/dbdd8c0.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/dbdd8c0.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -falcon==3.1.3 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/5a5524a.txt b/.riot/requirements/e312e0d.txt similarity index 90% rename from .riot/requirements/5a5524a.txt rename to .riot/requirements/e312e0d.txt index f4faac2d0ad..08cba09f0c4 100644 --- a/.riot/requirements/5a5524a.txt +++ b/.riot/requirements/e312e0d.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/5a5524a.in +# pip-compile --allow-unsafe --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/e312e0d.in # aiofiles==23.2.1 annotated-types==0.5.0 anyio==3.7.1 attrs==24.2.0 -certifi==2024.8.30 +certifi==2024.12.14 charset-normalizer==3.4.0 coverage[toml]==7.2.7 exceptiongroup==1.2.2 diff --git a/.riot/requirements/edd1c7f.txt b/.riot/requirements/edd1c7f.txt deleted file mode 100644 index 8be2caace8c..00000000000 --- a/.riot/requirements/edd1c7f.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/edd1c7f.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.28 -typing-extensions==4.10.0 diff --git a/.riot/requirements/eeaed0d.txt b/.riot/requirements/eeaed0d.txt new file mode 100644 index 00000000000..e91eca7391f --- /dev/null +++ b/.riot/requirements/eeaed0d.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/eeaed0d.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pyodbc==5.2.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/f066985.txt b/.riot/requirements/f066985.txt deleted file mode 100644 index debd8ebffab..00000000000 --- a/.riot/requirements/f066985.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/f066985.in -# -attrs==23.2.0 -coverage[toml]==7.4.1 -exceptiongroup==1.2.0 -glob2==0.7 -hypothesis==6.45.0 -iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 -mock==5.1.0 -more-itertools==8.10.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 -py==1.11.0 -pytest==8.0.0 -pytest-bdd==4.1.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/f15bee1.txt b/.riot/requirements/f15bee1.txt new file mode 100644 index 00000000000..ce381a0ee85 --- /dev/null +++ b/.riot/requirements/f15bee1.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f15bee1.in +# +attrs==24.3.0 +coverage[toml]==7.6.9 +greenlet==3.1.0 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +mysql-connector-python==9.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +psycopg2-binary==2.9.10 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +sqlalchemy==2.0.36 +typing-extensions==4.12.2 diff --git a/.riot/requirements/1912989.txt b/.riot/requirements/f781048.txt similarity index 66% rename from .riot/requirements/1912989.txt rename to .riot/requirements/f781048.txt index 3c74ad97a75..1b4a6f72034 100644 --- a/.riot/requirements/1912989.txt +++ b/.riot/requirements/f781048.txt @@ -2,37 +2,37 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1912989.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f781048.in # aiofiles==24.1.0 annotated-types==0.7.0 anyio==3.7.1 -attrs==24.2.0 -certifi==2024.8.30 +attrs==24.3.0 +certifi==2024.12.14 charset-normalizer==3.4.0 -coverage[toml]==7.6.3 -fastapi==0.115.2 +coverage[toml]==7.6.9 +fastapi==0.115.6 h11==0.14.0 -httpcore==1.0.6 +httpcore==1.0.7 httpx==0.27.2 hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==24.1 +packaging==24.2 pluggy==1.5.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pytest==8.3.3 +pydantic==2.10.3 +pydantic-core==2.27.1 +pytest==8.3.4 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-multipart==0.0.12 +pytest-randomly==3.16.0 +python-multipart==0.0.20 requests==2.32.3 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.40.0 +starlette==0.41.3 typing-extensions==4.12.2 urllib3==2.2.3 diff --git a/.riot/requirements/fb47988.txt b/.riot/requirements/fb47988.txt index 5bb283612e2..e9cc2d8236d 100644 --- a/.riot/requirements/fb47988.txt +++ b/.riot/requirements/fb47988.txt @@ -4,28 +4,28 @@ # # pip-compile --no-annotate .riot/requirements/fb47988.in # -attrs==23.2.0 -coverage[toml]==7.4.1 -exceptiongroup==1.2.0 +attrs==24.3.0 +coverage[toml]==7.6.9 +exceptiongroup==1.2.2 glob2==0.7 hypothesis==6.45.0 iniconfig==2.0.0 -mako==1.3.2 -markupsafe==2.1.5 +mako==1.3.8 +markupsafe==3.0.2 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.1.0 opentracing==2.4.0 -packaging==23.2 -parse==1.20.1 -parse-type==0.6.2 -pluggy==1.4.0 +packaging==24.2 +parse==1.20.2 +parse-type==0.6.4 +pluggy==1.5.0 py==1.11.0 -pytest==8.0.0 +pytest==8.3.4 pytest-bdd==6.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +six==1.17.0 sortedcontainers==2.4.0 -tomli==2.0.1 +tomli==2.2.1 diff --git a/.riot/requirements/fd74210.txt b/.riot/requirements/fd74210.txt deleted file mode 100644 index 33429bf35a5..00000000000 --- a/.riot/requirements/fd74210.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/fd74210.in -# -attrs==23.2.0 -coverage[toml]==7.4.4 -greenlet==3.0.3 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.3.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -psycopg2-binary==2.9.9 -pytest==8.1.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 diff --git a/ddtrace/internal/coverage/instrumentation.py b/ddtrace/internal/coverage/instrumentation.py index 4a846944efc..19bc61238ff 100644 --- a/ddtrace/internal/coverage/instrumentation.py +++ b/ddtrace/internal/coverage/instrumentation.py @@ -2,7 +2,9 @@ # Import are noqa'd otherwise some formatters will helpfully remove them -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 13): + from ddtrace.internal.coverage.instrumentation_py3_13 import instrument_all_lines # noqa +elif sys.version_info >= (3, 12): from ddtrace.internal.coverage.instrumentation_py3_12 import instrument_all_lines # noqa elif sys.version_info >= (3, 11): from ddtrace.internal.coverage.instrumentation_py3_11 import instrument_all_lines # noqa diff --git a/ddtrace/internal/coverage/instrumentation_py3_13.py b/ddtrace/internal/coverage/instrumentation_py3_13.py new file mode 100644 index 00000000000..2953a514e85 --- /dev/null +++ b/ddtrace/internal/coverage/instrumentation_py3_13.py @@ -0,0 +1,23 @@ +import dis +import sys +from types import CodeType +import typing as t + +from ddtrace.internal.injection import HookType +from ddtrace.internal.test_visibility.coverage_lines import CoverageLines + + +# This is primarily to make mypy happy without having to nest the rest of this module behind a version check +assert sys.version_info >= (3, 13) # nosec + +EXTENDED_ARG = dis.EXTENDED_ARG +IMPORT_NAME = dis.opmap["IMPORT_NAME"] +IMPORT_FROM = dis.opmap["IMPORT_FROM"] +RESUME = dis.opmap["RESUME"] +RETURN_CONST = dis.opmap["RETURN_CONST"] +EMPTY_MODULE_BYTES = bytes([RESUME, 0, RETURN_CONST, 0]) + + +def instrument_all_lines(code: CodeType, hook: HookType, path: str, package: str) -> t.Tuple[CodeType, CoverageLines]: + # No-op + return code, CoverageLines() diff --git a/hatch.toml b/hatch.toml index 614054dbfed..0baca1fd235 100644 --- a/hatch.toml +++ b/hatch.toml @@ -471,7 +471,7 @@ pytest = ["~=6.0", "~=7.0"] [[envs.pytest_plugin_v2.matrix]] -python = ["3.9", "3.10", "3.12", "3.13"] +python = ["3.9", "3.10", "3.12"] pytest = ["~=6.0", "~=7.0", "~=8.0"] [envs.snapshot_viewer] diff --git a/pyproject.toml b/pyproject.toml index 3c83cfc5067..a42954445ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,6 +175,7 @@ exclude-modules = ''' | ddtrace.internal.coverage.instrumentation_py3_10 | ddtrace.internal.coverage.instrumentation_py3_11 | ddtrace.internal.coverage.instrumentation_py3_12 + | ddtrace.internal.coverage.instrumentation_py3_13 ) ''' diff --git a/releasenotes/notes/313-enable-integrations-01990085769ea3f3.yaml b/releasenotes/notes/313-enable-integrations-01990085769ea3f3.yaml new file mode 100644 index 00000000000..cc3c4cc49ab --- /dev/null +++ b/releasenotes/notes/313-enable-integrations-01990085769ea3f3.yaml @@ -0,0 +1,17 @@ +--- +upgrade: + - | + Enables tests with Python 3.13 for the following integrations: + - aiomysql + - aiopg + - asyncpg + - avro + - confluent-kafka + - django + - falcon + - fastapi + - grpcio + - mysqldb + - protobuf + - pyodbc + - sqlalchemy diff --git a/riotfile.py b/riotfile.py index 69467452a1f..a9b978195d0 100644 --- a/riotfile.py +++ b/riotfile.py @@ -608,15 +608,30 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="falcon", command="pytest {cmdargs} tests/contrib/falcon", - pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ - "falcon": [ - "~=3.0.0", - "~=3.0", # latest 3.x - latest, - ], "pytest-randomly": latest, }, + venvs=[ + Venv( + pys=select_pys(min_version="3.7", max_version="3.12"), + pkgs={ + "falcon": [ + "~=3.0.0", + "~=3.0", # latest 3.x + latest, + ], + }, + ), + Venv( + pys=select_pys(min_version="3.13"), + pkgs={ + "falcon": [ + "~=4.0", # latest 4.x + latest, + ], + }, + ), + ], ), Venv( name="bottle", @@ -831,7 +846,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # django started supporting psycopg3 in 4.2 for versions >3.1.8 - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.8", max_version="3.13"), pkgs={ "django": ["~=4.2"], "psycopg": latest, @@ -1334,15 +1349,22 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/sqlalchemy", pkgs={ "pytest-randomly": latest, - "greenlet": "==3.0.3", + "psycopg2-binary": latest, + "mysql-connector-python": latest, + "sqlalchemy": latest, }, venvs=[ Venv( pys=select_pys(min_version="3.7", max_version="3.12"), pkgs={ + "greenlet": "==3.0.3", "sqlalchemy": ["~=1.3.0", latest], - "psycopg2-binary": latest, - "mysql-connector-python": latest, + }, + ), + Venv( + pys=select_pys(min_version="3.12"), + pkgs={ + "greenlet": "==3.1.0", }, ), ], @@ -1573,7 +1595,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT name="fastapi", command="pytest {cmdargs} tests/contrib/fastapi", pkgs={ - "httpx": latest, + "httpx": "<=0.27.2", "pytest-asyncio": "==0.21.1", "python-multipart": latest, "pytest-randomly": latest, @@ -1587,20 +1609,32 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # fastapi added support for Python 3.11 in 0.86.0 - pys=select_pys(min_version="3.11", max_version="3.12"), + pys=select_pys(min_version="3.11"), pkgs={"fastapi": ["~=0.86.0", latest], "anyio": ">=3.4.0,<4.0"}, ), ], ), Venv( name="aiomysql", - pys=select_pys(min_version="3.7", max_version="3.12"), command="pytest {cmdargs} tests/contrib/aiomysql", - pkgs={ - "pytest-randomly": latest, - "pytest-asyncio": "==0.21.1", - "aiomysql": ["~=0.1.0", latest], - }, + venvs=[ + Venv( + pys=select_pys(min_version="3.7", max_version="3.12"), + pkgs={ + "pytest-randomly": latest, + "pytest-asyncio": "==0.21.1", + "aiomysql": ["~=0.1.0", latest], + }, + ), + Venv( + pys=select_pys(min_version="3.13"), + pkgs={ + "pytest-randomly": latest, + "pytest-asyncio": latest, + "aiomysql": ["~=0.1.0", latest], + }, + ), + ], ), Venv( name="pytest", @@ -1721,6 +1755,11 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "msgpack": latest, "more_itertools": "<8.11.0", "pytest-randomly": latest, + "pytest-bdd": [ + ">=4.0,<5.0", + # FIXME: add support for v6.1 + ">=6.0,<6.1", + ], }, venvs=[ Venv( @@ -1749,7 +1788,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pys=select_pys(min_version="3.10", max_version="3.12"), pkgs={ "pytest-bdd": [ - ">=4.0,<5.0", # FIXME: add support for v6.1 ">=6.0,<6.1", ] @@ -1827,12 +1865,19 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # grpcio added support for Python 3.12 in 1.59 - pys=select_pys(min_version="3.12", max_version="3.12"), + pys="3.12", pkgs={ "grpcio": ["~=1.59.0", latest], "pytest-asyncio": "==0.23.7", }, ), + Venv( + # grpcio added support for Python 3.13 in 1.66.2 + pys=select_pys(min_version="3.13"), + pkgs={ + "grpcio": ["~=1.66.2", latest], + }, + ), ], ), Venv( @@ -2029,7 +2074,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.7", max_version="3.12"), + pys=select_pys(min_version="3.7"), pkgs={ "aiopg": ["~=1.0", "~=1.4.0"], }, @@ -2187,7 +2232,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="avro", - pys=select_pys(min_version="3.7", max_version="3.12"), + pys=select_pys(min_version="3.7"), command="pytest {cmdargs} tests/contrib/avro", pkgs={ "avro": latest, @@ -2197,7 +2242,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="protobuf", command="pytest {cmdargs} tests/contrib/protobuf", - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.8"), pkgs={ "protobuf": latest, "pytest-randomly": latest, @@ -2349,7 +2394,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"asyncpg": ["~=0.27", latest]}, ), Venv( - pys=select_pys(min_version="3.12", max_version="3.12"), + pys=select_pys(min_version="3.12"), pkgs={"asyncpg": [latest]}, ), ], @@ -2584,7 +2629,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # pyodbc added support for Python 3.11 in 4.0.35 - pys=select_pys(min_version="3.11", max_version="3.12"), + pys=select_pys(min_version="3.11"), pkgs={"pyodbc": [latest]}, ), ], @@ -2670,6 +2715,10 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={"mysqlclient": ["~=2.1", latest]}, ), + Venv( + pys=select_pys(min_version="3.13"), + pkgs={"mysqlclient": "==2.2.6"}, + ), ], ), Venv( @@ -2847,7 +2896,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"confluent-kafka": ["~=1.9.2", latest]}, ), # confluent-kafka added support for Python 3.11 in 2.0.2 - Venv(pys=select_pys(min_version="3.11", max_version="3.12"), pkgs={"confluent-kafka": latest}), + Venv(pys=select_pys(min_version="3.11"), pkgs={"confluent-kafka": latest}), ], ), ], diff --git a/tests/contrib/falcon/app/resources.py b/tests/contrib/falcon/app/resources.py index 74bb5b2a2e3..0b6df3bfaa5 100644 --- a/tests/contrib/falcon/app/resources.py +++ b/tests/contrib/falcon/app/resources.py @@ -1,5 +1,11 @@ import falcon +from ddtrace.internal.utils.version import parse_version + + +FALCON_VERSION = parse_version(falcon.__version__) +TEXT_ATTR = "text" if FALCON_VERSION >= (3, 0, 0) else "body" + class Resource200(object): """Throw a handled exception here to ensure our use of @@ -13,26 +19,26 @@ def on_get(self, req, resp, **kwargs): pass resp.status = falcon.HTTP_200 - resp.body = "Success" + setattr(resp, TEXT_ATTR, "Success") resp.append_header("my-response-header", "my_response_value") class DynamicURIResource(object): def on_get(self, req, resp, name): resp.status = falcon.HTTP_200 - resp.body = name + setattr(resp, TEXT_ATTR, name) class Resource201(object): def on_post(self, req, resp, **kwargs): resp.status = falcon.HTTP_201 - resp.body = "Success" + setattr(resp, TEXT_ATTR, "Success") class Resource500(object): def on_get(self, req, resp, **kwargs): resp.status = falcon.HTTP_500 - resp.body = "Failure" + setattr(resp, TEXT_ATTR, "Failure") class ResourceException(object): diff --git a/tests/contrib/kafka/test_kafka.py b/tests/contrib/kafka/test_kafka.py index d49f85f26b2..9d2786151da 100644 --- a/tests/contrib/kafka/test_kafka.py +++ b/tests/contrib/kafka/test_kafka.py @@ -37,6 +37,7 @@ SNAPSHOT_IGNORES = [ "metrics.kafka.message_offset", "meta.error.stack", + "meta.error.message", "meta.messaging.kafka.bootstrap.servers", "meta.peer.service", ]